You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by ti...@apache.org on 2021/01/16 14:39:42 UTC

[maven-surefire] branch master updated (02d4381 -> 8f9745e)

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

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


    from 02d4381  ci: JDK 8, JDK 11, JDK 15
     new c59ffee  [SUREFIRE-1847] Remove Base64 in the Encoder/Decoder and gain the performance for the communication flow: Plugin to Fork
     new 10d7e33  [jenkins] temporarily removed JDK 16EA, see INFRA-20981
     new 8f9745e  tests should not fail on concurrent access of src/test/java/.../*.ConsoleLoggerMock.java

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 Jenkinsfile                                        |    2 +-
 .../maven/plugin/surefire/SurefireHelper.java      |    2 +
 .../maven/plugin/surefire/SurefireProperties.java  |    5 +
 .../surefire/booterclient/BooterSerializer.java    |    2 +
 .../plugin/surefire/booterclient/ForkStarter.java  |   22 +-
 .../output/InPluginProcessDumpSingleton.java       |    7 +
 .../surefire/extensions/EventConsumerThread.java   |  926 +----------------
 .../surefire/extensions/LegacyForkChannel.java     |    2 +-
 .../surefire/extensions/LegacyForkNodeFactory.java |    2 +-
 .../plugin/surefire/extensions/StreamFeeder.java   |  123 +--
 .../surefire/extensions/SurefireForkChannel.java   |    2 +-
 .../extensions/SurefireForkNodeFactory.java        |    2 +-
 .../maven/surefire/stream/CommandEncoder.java      |  148 +++
 .../apache/maven/surefire/stream/EventDecoder.java |  468 +++++++++
 .../booterclient/ForkingRunListenerTest.java       |   22 +-
 .../plugin/surefire/booterclient/MainClass.java    |    2 +-
 .../TestLessInputStreamBuilderTest.java            |   35 +-
 .../TestProvidingInputStreamTest.java              |   46 +-
 .../booterclient/output/ForkClientTest.java        |   47 +-
 .../maven/plugin/surefire/extensions/E2ETest.java  |   16 +-
 .../extensions/EventConsumerThreadTest.java        | 1060 +-------------------
 .../extensions/ForkedProcessEventNotifierTest.java |  251 +----
 .../surefire/extensions/StreamFeederTest.java      |   39 +-
 .../org/apache/maven/surefire/JUnit4SuiteTest.java |    2 +
 .../maven/surefire/extensions/ForkChannelTest.java |   15 +-
 .../maven/surefire/stream/EventDecoderTest.java    |  785 +++++++++++++++
 maven-surefire-plugin/src/site/fml/faq.fml         |    4 +-
 .../maven/surefire/api/booter/Constants.java       |    6 +-
 .../surefire/api/booter/DumpErrorSingleton.java    |   27 +-
 .../surefire/api/booter/ForkingRunListener.java    |    2 +-
 .../api/booter/MasterProcessChannelEncoder.java    |    6 +-
 .../surefire/api/booter/MasterProcessCommand.java  |   54 +-
 .../surefire/api/fork}/ForkNodeArguments.java      |    6 +-
 .../apache/maven/surefire/api/report/RunMode.java  |   19 +
 .../surefire/api/stream/AbstractStreamDecoder.java |  735 ++++++++++++++
 .../surefire/api/stream/AbstractStreamEncoder.java |  191 ++++
 .../api/stream/MalformedChannelException.java      |    4 +-
 .../maven/surefire/api/stream/SegmentType.java}    |   12 +-
 .../surefire/api/util/internal/DumpFileUtils.java  |    1 +
 .../java/org/apache/maven/JUnit4SuiteTest.java     |    6 +-
 .../api/stream/AbstractStreamDecoderTest.java      |  692 +++++++++++++
 .../api/stream/AbstractStreamEncoderTest.java      |  331 ++++++
 .../maven/surefire/booter/BooterConstants.java     |    2 +-
 .../maven/surefire/booter/BooterDeserializer.java  |    5 +
 .../maven/surefire/booter/CommandReader.java       |    4 +-
 .../apache/maven/surefire/booter/ForkedBooter.java |   29 +-
 .../maven/surefire/booter/ForkedNodeArg.java       |   97 ++
 .../maven/surefire/booter/PropertiesWrapper.java   |    2 +-
 .../surefire/booter/spi/CommandChannelDecoder.java |   86 ++
 ...hannelEncoder.java => EventChannelEncoder.java} |  268 ++---
 .../spi/LegacyMasterProcessChannelDecoder.java     |  190 ----
 ...LegacyMasterProcessChannelProcessorFactory.java |   12 +-
 ...refireMasterProcessChannelProcessorFactory.java |   14 +-
 .../surefire/booter/stream/CommandDecoder.java     |  276 +++++
 .../maven/surefire/booter/stream/EventEncoder.java |   78 ++
 .../maven/surefire/booter/CommandReaderTest.java   |   44 +-
 .../surefire/booter/ForkedBooterMockTest.java      |   33 +-
 .../maven/surefire/booter/JUnit4SuiteTest.java     |    8 +-
 .../booter/spi/CommandChannelDecoderTest.java      |  519 ++++++++++
 ...coderTest.java => EventChannelEncoderTest.java} |  227 +----
 .../spi/LegacyMasterProcessChannelDecoderTest.java |  243 -----
 .../resources/binary-commands/75171711-encoder.bin |  Bin 0 -> 851 bytes
 .../maven/surefire/extensions/ForkChannel.java     |    1 +
 .../maven/surefire/extensions/ForkNodeFactory.java |    2 +
 surefire-extensions-spi/pom.xml                    |    5 +
 .../spi/MasterProcessChannelProcessorFactory.java  |   10 +-
 .../its/JUnitPlatformStreamCorruptionIT.java       |    2 +-
 .../plugin/surefire/log/api/PrintStreamLogger.java |    2 +-
 68 files changed, 5090 insertions(+), 3198 deletions(-)
 create mode 100644 maven-surefire-common/src/main/java/org/apache/maven/surefire/stream/CommandEncoder.java
 create mode 100644 maven-surefire-common/src/main/java/org/apache/maven/surefire/stream/EventDecoder.java
 create mode 100644 maven-surefire-common/src/test/java/org/apache/maven/surefire/stream/EventDecoderTest.java
 rename {surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions => surefire-api/src/main/java/org/apache/maven/surefire/api/fork}/ForkNodeArguments.java (92%)
 create mode 100644 surefire-api/src/main/java/org/apache/maven/surefire/api/stream/AbstractStreamDecoder.java
 create mode 100644 surefire-api/src/main/java/org/apache/maven/surefire/api/stream/AbstractStreamEncoder.java
 copy maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/DataZT1A.java => surefire-api/src/main/java/org/apache/maven/surefire/api/stream/MalformedChannelException.java (88%)
 copy surefire-api/src/{test/java/org/apache/maven/surefire/api/testdata/DataZT1A.java => main/java/org/apache/maven/surefire/api/stream/SegmentType.java} (79%)
 create mode 100644 surefire-api/src/test/java/org/apache/maven/surefire/api/stream/AbstractStreamDecoderTest.java
 create mode 100644 surefire-api/src/test/java/org/apache/maven/surefire/api/stream/AbstractStreamEncoderTest.java
 create mode 100644 surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedNodeArg.java
 create mode 100644 surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/CommandChannelDecoder.java
 rename surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/{LegacyMasterProcessChannelEncoder.java => EventChannelEncoder.java} (61%)
 delete mode 100644 surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelDecoder.java
 create mode 100644 surefire-booter/src/main/java/org/apache/maven/surefire/booter/stream/CommandDecoder.java
 create mode 100644 surefire-booter/src/main/java/org/apache/maven/surefire/booter/stream/EventEncoder.java
 create mode 100644 surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/CommandChannelDecoderTest.java
 rename surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/{LegacyMasterProcessChannelEncoderTest.java => EventChannelEncoderTest.java} (81%)
 delete mode 100644 surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelDecoderTest.java
 create mode 100644 surefire-booter/src/test/resources/binary-commands/75171711-encoder.bin


[maven-surefire] 01/03: [SUREFIRE-1847] Remove Base64 in the Encoder/Decoder and gain the performance for the communication flow: Plugin to Fork

Posted by ti...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit c59ffeed87e938259823178e6a2e6010d8138f9f
Author: tibordigana <ti...@gmail.com>
AuthorDate: Wed Nov 25 08:59:01 2020 +0100

    [SUREFIRE-1847] Remove Base64 in the Encoder/Decoder and gain the performance for the communication flow: Plugin to Fork
---
 .../maven/plugin/surefire/SurefireHelper.java      |    2 +
 .../maven/plugin/surefire/SurefireProperties.java  |    5 +
 .../surefire/booterclient/BooterSerializer.java    |    2 +
 .../plugin/surefire/booterclient/ForkStarter.java  |   22 +-
 .../output/InPluginProcessDumpSingleton.java       |    7 +
 .../surefire/extensions/EventConsumerThread.java   |  926 +----------------
 .../surefire/extensions/LegacyForkChannel.java     |    2 +-
 .../surefire/extensions/LegacyForkNodeFactory.java |    2 +-
 .../plugin/surefire/extensions/StreamFeeder.java   |  123 +--
 .../surefire/extensions/SurefireForkChannel.java   |    2 +-
 .../extensions/SurefireForkNodeFactory.java        |    2 +-
 .../maven/surefire/stream/CommandEncoder.java      |  148 +++
 .../apache/maven/surefire/stream/EventDecoder.java |  468 +++++++++
 .../booterclient/ForkingRunListenerTest.java       |   22 +-
 .../plugin/surefire/booterclient/MainClass.java    |    2 +-
 .../TestLessInputStreamBuilderTest.java            |   35 +-
 .../TestProvidingInputStreamTest.java              |   46 +-
 .../booterclient/output/ForkClientTest.java        |   19 +-
 .../maven/plugin/surefire/extensions/E2ETest.java  |   16 +-
 .../extensions/EventConsumerThreadTest.java        | 1060 +-------------------
 .../extensions/ForkedProcessEventNotifierTest.java |  223 +---
 .../surefire/extensions/StreamFeederTest.java      |   39 +-
 .../org/apache/maven/surefire/JUnit4SuiteTest.java |    2 +
 .../maven/surefire/extensions/ForkChannelTest.java |   15 +-
 .../maven/surefire/stream/EventDecoderTest.java    |  785 +++++++++++++++
 maven-surefire-plugin/src/site/fml/faq.fml         |    4 +-
 .../maven/surefire/api/booter/Constants.java       |    6 +-
 .../surefire/api/booter/DumpErrorSingleton.java    |   27 +-
 .../surefire/api/booter/ForkingRunListener.java    |    2 +-
 .../api/booter/MasterProcessChannelEncoder.java    |    6 +-
 .../surefire/api/booter/MasterProcessCommand.java  |   54 +-
 .../surefire/api/fork}/ForkNodeArguments.java      |    6 +-
 .../apache/maven/surefire/api/report/RunMode.java  |   19 +
 .../surefire/api/stream/AbstractStreamDecoder.java |  735 ++++++++++++++
 .../surefire/api/stream/AbstractStreamEncoder.java |  191 ++++
 .../MalformedChannelException.java}                |   13 +-
 .../Constants.java => stream/SegmentType.java}     |   21 +-
 .../surefire/api/util/internal/DumpFileUtils.java  |    1 +
 .../java/org/apache/maven/JUnit4SuiteTest.java     |    6 +-
 .../api/stream/AbstractStreamDecoderTest.java      |  692 +++++++++++++
 .../api/stream/AbstractStreamEncoderTest.java      |  331 ++++++
 .../maven/surefire/booter/BooterConstants.java     |    2 +-
 .../maven/surefire/booter/BooterDeserializer.java  |    5 +
 .../maven/surefire/booter/CommandReader.java       |    4 +-
 .../apache/maven/surefire/booter/ForkedBooter.java |   29 +-
 .../maven/surefire/booter/ForkedNodeArg.java       |   97 ++
 .../maven/surefire/booter/PropertiesWrapper.java   |    2 +-
 .../surefire/booter/spi/CommandChannelDecoder.java |   86 ++
 ...hannelEncoder.java => EventChannelEncoder.java} |  268 ++---
 .../spi/LegacyMasterProcessChannelDecoder.java     |  190 ----
 ...LegacyMasterProcessChannelProcessorFactory.java |   12 +-
 ...refireMasterProcessChannelProcessorFactory.java |   14 +-
 .../surefire/booter/stream/CommandDecoder.java     |  276 +++++
 .../maven/surefire/booter/stream/EventEncoder.java |   78 ++
 .../maven/surefire/booter/CommandReaderTest.java   |   44 +-
 .../surefire/booter/ForkedBooterMockTest.java      |   33 +-
 .../maven/surefire/booter/JUnit4SuiteTest.java     |    8 +-
 .../booter/spi/CommandChannelDecoderTest.java      |  519 ++++++++++
 ...coderTest.java => EventChannelEncoderTest.java} |  227 +----
 .../spi/LegacyMasterProcessChannelDecoderTest.java |  243 -----
 .../resources/binary-commands/75171711-encoder.bin |  Bin 0 -> 851 bytes
 .../maven/surefire/extensions/ForkChannel.java     |    1 +
 .../maven/surefire/extensions/ForkNodeFactory.java |    2 +
 surefire-extensions-spi/pom.xml                    |    5 +
 .../spi/MasterProcessChannelProcessorFactory.java  |   10 +-
 .../its/JUnitPlatformStreamCorruptionIT.java       |    2 +-
 .../plugin/surefire/log/api/PrintStreamLogger.java |    2 +-
 67 files changed, 5061 insertions(+), 3187 deletions(-)

diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireHelper.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireHelper.java
index d0cac4d..9149946 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireHelper.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireHelper.java
@@ -64,6 +64,8 @@ public final class SurefireHelper
 
     public static final String DUMP_FILENAME = DUMP_FILE_DATE + DUMP_FILE_EXT;
 
+    public static final String EVENTS_BINARY_DUMP_FILENAME_FORMATTER = DUMP_FILE_DATE + "-jvmRun%d-events.bin";
+
     /**
      * The maximum path that does not require long path prefix on Windows.<br>
      * See {@code sun/nio/fs/WindowsPath} in
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireProperties.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireProperties.java
index a880c0d..cd2f91f 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireProperties.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireProperties.java
@@ -201,6 +201,11 @@ public class SurefireProperties
         }
     }
 
+    public void setProperty( String key, int value )
+    {
+        setProperty( key, String.valueOf( value ) );
+    }
+
     public void setProperty( String key, Long value )
     {
         if ( value != null )
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/BooterSerializer.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/BooterSerializer.java
index 1e00f16..1335a82 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/BooterSerializer.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/BooterSerializer.java
@@ -50,6 +50,7 @@ import static org.apache.maven.surefire.booter.BooterConstants.FAIL_FAST_COUNT;
 import static org.apache.maven.surefire.booter.BooterConstants.FAILIFNOTESTS;
 import static org.apache.maven.surefire.booter.BooterConstants.FORKTESTSET;
 import static org.apache.maven.surefire.booter.BooterConstants.FORKTESTSET_PREFER_TESTS_FROM_IN_STREAM;
+import static org.apache.maven.surefire.booter.BooterConstants.FORK_NUMBER;
 import static org.apache.maven.surefire.booter.BooterConstants.INCLUDES_PROPERTY_PREFIX;
 import static org.apache.maven.surefire.booter.BooterConstants.ISTRIMSTACKTRACE;
 import static org.apache.maven.surefire.booter.BooterConstants.MAIN_CLI_OPTIONS;
@@ -168,6 +169,7 @@ class BooterSerializer
         ReporterConfiguration reporterConfiguration = providerConfiguration.getReporterConfiguration();
         boolean rep = reporterConfiguration.isTrimStackTrace();
         File reportsDirectory = replaceForkThreadsInPath( reporterConfiguration.getReportsDirectory(), forkNumber );
+        properties.setProperty( FORK_NUMBER, forkNumber );
         properties.setProperty( ISTRIMSTACKTRACE, rep );
         properties.setProperty( REPORTSDIRECTORY, reportsDirectory );
         ClassLoaderConfiguration classLoaderConfig = startupConfiguration.getClassLoaderConfiguration();
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 1ac3917..200d2a8 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
@@ -45,7 +45,7 @@ import org.apache.maven.surefire.extensions.CloseableDaemonThread;
 import org.apache.maven.surefire.extensions.EventHandler;
 import org.apache.maven.surefire.extensions.ForkChannel;
 import org.apache.maven.surefire.extensions.ForkNodeFactory;
-import org.apache.maven.surefire.extensions.ForkNodeArguments;
+import org.apache.maven.surefire.api.fork.ForkNodeArguments;
 import org.apache.maven.surefire.extensions.util.CommandlineExecutor;
 import org.apache.maven.surefire.extensions.util.CommandlineStreams;
 import org.apache.maven.surefire.extensions.util.CountdownCloseable;
@@ -574,7 +574,8 @@ public class ForkStarter
         File dumpLogDir = replaceForkThreadsInPath( startupReportConfiguration.getReportsDirectory(), forkNumber );
         try
         {
-            ForkNodeArguments forkNodeArguments = new ForkedNodeArg( forkNumber, dumpLogDir, randomUUID().toString() );
+            ForkNodeArguments forkNodeArguments =
+                new ForkedNodeArg( forkConfiguration.isDebug(), forkNumber, dumpLogDir, randomUUID().toString() );
             forkChannel = forkNodeFactory.createForkChannel( forkNodeArguments );
             closer.addCloseable( forkChannel );
             tempDir = forkConfiguration.getTempDirectory().getCanonicalPath();
@@ -884,9 +885,11 @@ public class ForkStarter
         private final int forkChannelId;
         private final File dumpLogDir;
         private final String sessionId;
+        private final boolean debug;
 
-        ForkedNodeArg( int forkChannelId, File dumpLogDir, String sessionId )
+        ForkedNodeArg( boolean debug, int forkChannelId, File dumpLogDir, String sessionId )
         {
+            this.debug = debug;
             this.forkChannelId = forkChannelId;
             this.dumpLogDir = dumpLogDir;
             this.sessionId = sessionId;
@@ -932,5 +935,18 @@ public class ForkStarter
         {
             return log;
         }
+
+        @Override
+        public File getEventStreamBinaryFile()
+        {
+            return debug ? InPluginProcessDumpSingleton.getSingleton()
+                .getEventStreamBinaryFile( dumpLogDir, forkChannelId ) : null;
+        }
+
+        @Override
+        public File getCommandStreamBinaryFile()
+        {
+            return null;
+        }
     }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/InPluginProcessDumpSingleton.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/InPluginProcessDumpSingleton.java
index 372c046..05c4235 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/InPluginProcessDumpSingleton.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/InPluginProcessDumpSingleton.java
@@ -28,6 +28,7 @@ import static org.apache.maven.plugin.surefire.SurefireHelper.DUMP_FILENAME;
 import static org.apache.maven.plugin.surefire.SurefireHelper.DUMP_FILENAME_FORMATTER;
 import static org.apache.maven.plugin.surefire.SurefireHelper.DUMPSTREAM_FILENAME;
 import static org.apache.maven.plugin.surefire.SurefireHelper.DUMPSTREAM_FILENAME_FORMATTER;
+import static org.apache.maven.plugin.surefire.SurefireHelper.EVENTS_BINARY_DUMP_FILENAME_FORMATTER;
 
 /**
  * Reports errors to dump file.
@@ -82,6 +83,12 @@ public final class InPluginProcessDumpSingleton
         DumpFileUtils.dumpException( t, msg == null ? "null" : msg, dump );
     }
 
+    public File getEventStreamBinaryFile( File reportsDirectory, int jvmRun )
+    {
+        reportsDirectory.mkdirs();
+        return new File( reportsDirectory, format( EVENTS_BINARY_DUMP_FILENAME_FORMATTER, jvmRun ) );
+    }
+
     private File newDumpStreamFile( File reportsDirectory )
     {
         return new File( reportsDirectory, DUMPSTREAM_FILENAME );
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThread.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThread.java
index 8b855d8..1371dbb 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThread.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThread.java
@@ -19,142 +19,28 @@ package org.apache.maven.plugin.surefire.extensions;
  * under the License.
  */
 
-import org.apache.maven.plugin.surefire.booterclient.output.DeserializedStacktraceWriter;
-import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
-import org.apache.maven.surefire.api.booter.ForkedProcessEventType;
-import org.apache.maven.surefire.api.event.ConsoleDebugEvent;
-import org.apache.maven.surefire.api.event.ConsoleErrorEvent;
-import org.apache.maven.surefire.api.event.ConsoleInfoEvent;
-import org.apache.maven.surefire.api.event.ConsoleWarningEvent;
-import org.apache.maven.surefire.api.event.ControlByeEvent;
-import org.apache.maven.surefire.api.event.ControlNextTestEvent;
-import org.apache.maven.surefire.api.event.ControlStopOnNextTestEvent;
 import org.apache.maven.surefire.api.event.Event;
-import org.apache.maven.surefire.api.event.JvmExitErrorEvent;
-import org.apache.maven.surefire.api.event.StandardStreamErrEvent;
-import org.apache.maven.surefire.api.event.StandardStreamErrWithNewLineEvent;
-import org.apache.maven.surefire.api.event.StandardStreamOutEvent;
-import org.apache.maven.surefire.api.event.StandardStreamOutWithNewLineEvent;
-import org.apache.maven.surefire.api.event.SystemPropertyEvent;
-import org.apache.maven.surefire.api.event.TestAssumptionFailureEvent;
-import org.apache.maven.surefire.api.event.TestErrorEvent;
-import org.apache.maven.surefire.api.event.TestFailedEvent;
-import org.apache.maven.surefire.api.event.TestSkippedEvent;
-import org.apache.maven.surefire.api.event.TestStartingEvent;
-import org.apache.maven.surefire.api.event.TestSucceededEvent;
-import org.apache.maven.surefire.api.event.TestsetCompletedEvent;
-import org.apache.maven.surefire.api.event.TestsetStartingEvent;
-import org.apache.maven.surefire.api.report.RunMode;
-import org.apache.maven.surefire.api.report.StackTraceWriter;
-import org.apache.maven.surefire.api.report.TestSetReportEntry;
+import org.apache.maven.surefire.api.fork.ForkNodeArguments;
+import org.apache.maven.surefire.api.stream.AbstractStreamDecoder.Memento;
 import org.apache.maven.surefire.extensions.CloseableDaemonThread;
 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.stream.EventDecoder;
 
-import javax.annotation.Nonnegative;
 import javax.annotation.Nonnull;
 import java.io.EOFException;
-import java.io.File;
 import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
 import java.nio.channels.ReadableByteChannel;
-import java.nio.charset.Charset;
-import java.nio.charset.CharsetDecoder;
-import java.nio.charset.CoderResult;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import static java.lang.Math.max;
-import static java.lang.Math.min;
-import static java.nio.charset.CodingErrorAction.REPLACE;
-import static java.nio.charset.StandardCharsets.US_ASCII;
-import static org.apache.maven.plugin.surefire.extensions.EventConsumerThread.StreamReadStatus.OVERFLOW;
-import static org.apache.maven.plugin.surefire.extensions.EventConsumerThread.StreamReadStatus.UNDERFLOW;
-import static org.apache.maven.surefire.api.booter.Constants.DEFAULT_STREAM_ENCODING;
-import static org.apache.maven.surefire.api.booter.Constants.MAGIC_NUMBER_BYTES;
-import static org.apache.maven.surefire.api.report.CategorizedReportEntry.reportEntry;
 
 /**
  *
  */
 public class EventConsumerThread extends CloseableDaemonThread
 {
-    private static final String[] JVM_ERROR_PATTERNS =
-        {
-            "could not create the java virtual machine",
-            "error occurred during initialization", // of VM, of boot layer
-            "error:", // general errors
-            "could not reserve enough space", "could not allocate", "unable to allocate", // memory errors
-            "java.lang.module.findexception" // JPMS errors
-        };
-
-    private static final String PRINTABLE_JVM_NATIVE_STREAM = "Listening for transport dt_socket at address:";
-
-    private static final SegmentType[] EVENT_WITHOUT_DATA = new SegmentType[] {
-        SegmentType.END_OF_FRAME
-    };
-
-    private static final SegmentType[] EVENT_WITH_ERROR_TRACE = new SegmentType[] {
-        SegmentType.STRING_ENCODING,
-        SegmentType.DATA_STRING,
-        SegmentType.DATA_STRING,
-        SegmentType.DATA_STRING,
-        SegmentType.END_OF_FRAME
-    };
-
-    private static final SegmentType[] EVENT_WITH_ONE_STRING = new SegmentType[] {
-        SegmentType.STRING_ENCODING,
-        SegmentType.DATA_STRING,
-        SegmentType.END_OF_FRAME
-    };
-
-    private static final SegmentType[] EVENT_WITH_RUNMODE_AND_ONE_STRING = new SegmentType[] {
-        SegmentType.RUN_MODE,
-        SegmentType.STRING_ENCODING,
-        SegmentType.DATA_STRING,
-        SegmentType.END_OF_FRAME
-    };
-
-    private static final SegmentType[] EVENT_WITH_RUNMODE_AND_TWO_STRINGS = new SegmentType[] {
-        SegmentType.RUN_MODE,
-        SegmentType.STRING_ENCODING,
-        SegmentType.DATA_STRING,
-        SegmentType.DATA_STRING,
-        SegmentType.END_OF_FRAME
-    };
-
-    private static final SegmentType[] EVENT_TEST_CONTROL = new SegmentType[] {
-        SegmentType.RUN_MODE,
-        SegmentType.STRING_ENCODING,
-        SegmentType.DATA_STRING,
-        SegmentType.DATA_STRING,
-        SegmentType.DATA_STRING,
-        SegmentType.DATA_STRING,
-        SegmentType.DATA_STRING,
-        SegmentType.DATA_STRING,
-        SegmentType.DATA_INT,
-        SegmentType.DATA_STRING,
-        SegmentType.DATA_STRING,
-        SegmentType.DATA_STRING,
-        SegmentType.END_OF_FRAME
-    };
-
-    private static final int BUFFER_SIZE = 1024;
-    private static final byte[] DEFAULT_STREAM_ENCODING_BYTES = DEFAULT_STREAM_ENCODING.name().getBytes( US_ASCII );
-    private static final int DELIMITER_LENGTH = 1;
-    private static final int BYTE_LENGTH = 1;
-    private static final int INT_LENGTH = 4;
-    private static final int NO_POSITION = -1;
-
     private final ReadableByteChannel channel;
     private final EventHandler<Event> eventHandler;
     private final CountdownCloseable countdownCloseable;
+    private final EventDecoder decoder;
     private final ForkNodeArguments arguments;
     private volatile boolean disabled;
 
@@ -165,6 +51,7 @@ public class EventConsumerThread extends CloseableDaemonThread
                                 @Nonnull ForkNodeArguments arguments )
     {
         super( threadName );
+        decoder = new EventDecoder( channel, arguments );
         this.channel = channel;
         this.eventHandler = eventHandler;
         this.countdownCloseable = countdownCloseable;
@@ -175,809 +62,42 @@ public class EventConsumerThread extends CloseableDaemonThread
     public void run()
     {
         try ( ReadableByteChannel stream = channel;
-              CountdownCloseable c = countdownCloseable; )
-        {
-            decode();
-        }
-        catch ( IOException e )
-        {
-            // not needed
-        }
-    }
-
-    @Override
-    public void disable()
-    {
-        disabled = true;
-    }
-
-    @Override
-    public void close() throws IOException
-    {
-        channel.close();
-    }
-
-    @SuppressWarnings( "checkstyle:innerassignment" )
-    private void decode() throws IOException
-    {
-        Map<Segment, ForkedProcessEventType> eventTypes = mapEventTypes();
-        Map<Segment, RunMode> runModes = mapRunModes();
-        Memento memento = new Memento();
-        memento.bb.limit( 0 );
-
-        do
-        {
-            try
-            {
-                ForkedProcessEventType eventType = readEventType( eventTypes, memento );
-                if ( eventType == null )
-                {
-                    throw new MalformedFrameException( memento.line.positionByteBuffer, memento.bb.position() );
-                }
-                RunMode runMode = null;
-                for ( SegmentType segmentType : nextSegmentType( eventType ) )
-                {
-                    if ( segmentType == null )
-                    {
-                        break;
-                    }
-
-                    switch ( segmentType )
-                    {
-                        case RUN_MODE:
-                            runMode = runModes.get( readSegment( memento ) );
-                            break;
-                        case STRING_ENCODING:
-                            memento.setCharset( readCharset( memento ) );
-                            break;
-                        case DATA_STRING:
-                            memento.data.add( readString( memento ) );
-                            break;
-                        case DATA_INT:
-                            memento.data.add( readInteger( memento ) );
-                            break;
-                        case END_OF_FRAME:
-                            memento.line.positionByteBuffer = memento.bb.position();
-                            if ( !disabled )
-                            {
-                                eventHandler.handleEvent( toEvent( eventType, runMode, memento.data ) );
-                            }
-                            break;
-                        default:
-                            memento.line.positionByteBuffer = NO_POSITION;
-                            arguments.dumpStreamText( "Unknown enum ("
-                                + ForkedProcessEventType.class.getSimpleName()
-                                + ") "
-                                + segmentType );
-                    }
-                }
-            }
-            catch ( MalformedFrameException e )
-            {
-                if ( e.hasValidPositions() )
-                {
-                    int length = e.readTo - e.readFrom;
-                    memento.line.write( memento.bb, e.readFrom, length );
-                }
-            }
-            catch ( RuntimeException e )
-            {
-                arguments.dumpStreamException( e );
-            }
-            catch ( IOException e )
-            {
-                printRemainingStream( memento );
-                throw e;
-            }
-            finally
-            {
-                memento.reset();
-            }
-        }
-        while ( true );
-    }
-
-    protected ForkedProcessEventType readEventType( Map<Segment, ForkedProcessEventType> eventTypes, Memento memento )
-        throws IOException, MalformedFrameException
-    {
-        int readCount = DELIMITER_LENGTH + MAGIC_NUMBER_BYTES.length + DELIMITER_LENGTH
-            + BYTE_LENGTH + DELIMITER_LENGTH;
-        read( memento, readCount );
-        checkHeader( memento );
-        return eventTypes.get( readSegment( memento ) );
-    }
-
-    protected String readString( Memento memento ) throws IOException, MalformedFrameException
-    {
-        memento.cb.clear();
-        int readCount = readInt( memento );
-        read( memento, readCount + DELIMITER_LENGTH );
-
-        final String string;
-        if ( readCount == 0 )
-        {
-            string = "";
-        }
-        else if ( readCount == 1 )
-        {
-            read( memento, 1 );
-            byte oneChar = memento.bb.get();
-            string = oneChar == 0 ? null : String.valueOf( (char) oneChar );
-        }
-        else
-        {
-            string = readString( memento, readCount );
-        }
-
-        checkDelimiter( memento );
-        return string;
-    }
-
-    @Nonnull
-    @SuppressWarnings( "checkstyle:magicnumber" )
-    protected Segment readSegment( Memento memento ) throws IOException, MalformedFrameException
-    {
-        int readCount = readByte( memento ) & 0xff;
-        read( memento, readCount + DELIMITER_LENGTH );
-        ByteBuffer bb = memento.bb;
-        Segment segment = new Segment( bb.array(), bb.arrayOffset() + bb.position(), readCount );
-        bb.position( bb.position() + readCount );
-        checkDelimiter( memento );
-        return segment;
-    }
-
-    @Nonnull
-    @SuppressWarnings( "checkstyle:magicnumber" )
-    protected Charset readCharset( Memento memento ) throws IOException, MalformedFrameException
-    {
-        int length = readByte( memento ) & 0xff;
-        read( memento, length + DELIMITER_LENGTH );
-        ByteBuffer bb = memento.bb;
-        byte[] array = bb.array();
-        int offset = bb.arrayOffset() + bb.position();
-        bb.position( bb.position() + length );
-        boolean isDefaultEncoding = false;
-        if ( length == DEFAULT_STREAM_ENCODING_BYTES.length )
-        {
-            isDefaultEncoding = true;
-            for ( int i = 0; i < length; i++ )
-            {
-                isDefaultEncoding &= DEFAULT_STREAM_ENCODING_BYTES[i] == array[offset + i];
-            }
-        }
-
-        try
-        {
-            Charset charset =
-                isDefaultEncoding
-                    ? DEFAULT_STREAM_ENCODING
-                    : Charset.forName( new String( array, offset, length, US_ASCII ) );
-
-            checkDelimiter( memento );
-            return charset;
-        }
-        catch ( IllegalArgumentException e )
-        {
-            throw new MalformedFrameException( memento.line.positionByteBuffer, bb.position() );
-        }
-    }
-
-    private static void checkHeader( Memento memento ) throws MalformedFrameException
-    {
-        ByteBuffer bb = memento.bb;
-
-        checkDelimiter( memento );
-
-        int shift = 0;
-        try
-        {
-            for ( int start = bb.arrayOffset() + bb.position(), length = MAGIC_NUMBER_BYTES.length;
-                  shift < length; shift++ )
-            {
-                if ( bb.array()[shift + start] != MAGIC_NUMBER_BYTES[shift] )
-                {
-                    throw new MalformedFrameException( memento.line.positionByteBuffer, bb.position() + shift );
-                }
-            }
-        }
-        finally
-        {
-            bb.position( bb.position() + shift );
-        }
-
-        checkDelimiter( memento );
-    }
-
-    @SuppressWarnings( "checkstyle:magicnumber" )
-    private static void checkDelimiter( Memento memento ) throws MalformedFrameException
-    {
-        ByteBuffer bb = memento.bb;
-        if ( ( 0xff & bb.get() ) != ':' )
-        {
-            throw new MalformedFrameException( memento.line.positionByteBuffer, bb.position() );
-        }
-    }
-
-    static SegmentType[] nextSegmentType( ForkedProcessEventType eventType )
-    {
-        switch ( eventType )
-        {
-            case BOOTERCODE_BYE:
-            case BOOTERCODE_STOP_ON_NEXT_TEST:
-            case BOOTERCODE_NEXT_TEST:
-                return EVENT_WITHOUT_DATA;
-            case BOOTERCODE_CONSOLE_ERROR:
-            case BOOTERCODE_JVM_EXIT_ERROR:
-                return EVENT_WITH_ERROR_TRACE;
-            case BOOTERCODE_CONSOLE_INFO:
-            case BOOTERCODE_CONSOLE_DEBUG:
-            case BOOTERCODE_CONSOLE_WARNING:
-                return EVENT_WITH_ONE_STRING;
-            case BOOTERCODE_STDOUT:
-            case BOOTERCODE_STDOUT_NEW_LINE:
-            case BOOTERCODE_STDERR:
-            case BOOTERCODE_STDERR_NEW_LINE:
-                return EVENT_WITH_RUNMODE_AND_ONE_STRING;
-            case BOOTERCODE_SYSPROPS:
-                return EVENT_WITH_RUNMODE_AND_TWO_STRINGS;
-            case BOOTERCODE_TESTSET_STARTING:
-            case BOOTERCODE_TESTSET_COMPLETED:
-            case BOOTERCODE_TEST_STARTING:
-            case BOOTERCODE_TEST_SUCCEEDED:
-            case BOOTERCODE_TEST_FAILED:
-            case BOOTERCODE_TEST_SKIPPED:
-            case BOOTERCODE_TEST_ERROR:
-            case BOOTERCODE_TEST_ASSUMPTIONFAILURE:
-                return EVENT_TEST_CONTROL;
-            default:
-                throw new IllegalArgumentException( "Unknown enum " + eventType );
-        }
-    }
-
-    protected StreamReadStatus read( Memento memento, int recommendedCount ) throws IOException
-    {
-        ByteBuffer buffer = memento.bb;
-        if ( buffer.remaining() >= recommendedCount && buffer.position() != 0 )
-        {
-            return OVERFLOW;
-        }
-        else
-        {
-            if ( buffer.position() != 0 && recommendedCount > buffer.capacity() - buffer.position() )
-            {
-                buffer.compact().flip();
-                memento.line.positionByteBuffer = 0;
-            }
-            int mark = buffer.position();
-            buffer.position( buffer.limit() );
-            buffer.limit( buffer.capacity() );
-            boolean isEnd = false;
-            while ( !isEnd && buffer.position() - mark < recommendedCount && buffer.position() != buffer.limit() )
-            {
-                isEnd = channel.read( buffer ) == -1;
-            }
-
-            buffer.limit( buffer.position() );
-            buffer.position( mark );
-            int readBytes = buffer.remaining();
-
-            if ( isEnd && readBytes < recommendedCount )
-            {
-                throw new EOFException();
-            }
-            else
-            {
-                return readBytes >= recommendedCount ? OVERFLOW : UNDERFLOW;
-            }
-        }
-    }
-
-    static Event toEvent( ForkedProcessEventType eventType, RunMode runMode, List<Object> args )
-    {
-        switch ( eventType )
-        {
-            case BOOTERCODE_BYE:
-                return new ControlByeEvent();
-            case BOOTERCODE_STOP_ON_NEXT_TEST:
-                return new ControlStopOnNextTestEvent();
-            case BOOTERCODE_NEXT_TEST:
-                return new ControlNextTestEvent();
-            case BOOTERCODE_CONSOLE_ERROR:
-                return new ConsoleErrorEvent( toStackTraceWriter( args ) );
-            case BOOTERCODE_JVM_EXIT_ERROR:
-                return new JvmExitErrorEvent( toStackTraceWriter( args ) );
-            case BOOTERCODE_CONSOLE_INFO:
-                return new ConsoleInfoEvent( (String) args.get( 0 ) );
-            case BOOTERCODE_CONSOLE_DEBUG:
-                return new ConsoleDebugEvent( (String) args.get( 0 ) );
-            case BOOTERCODE_CONSOLE_WARNING:
-                return new ConsoleWarningEvent( (String) args.get( 0 ) );
-            case BOOTERCODE_STDOUT:
-                return new StandardStreamOutEvent( runMode, (String) args.get( 0 ) );
-            case BOOTERCODE_STDOUT_NEW_LINE:
-                return new StandardStreamOutWithNewLineEvent( runMode, (String) args.get( 0 ) );
-            case BOOTERCODE_STDERR:
-                return new StandardStreamErrEvent( runMode, (String) args.get( 0 ) );
-            case BOOTERCODE_STDERR_NEW_LINE:
-                return new StandardStreamErrWithNewLineEvent( runMode, (String) args.get( 0 ) );
-            case BOOTERCODE_SYSPROPS:
-                String key = (String) args.get( 0 );
-                String value = (String) args.get( 1 );
-                return new SystemPropertyEvent( runMode, key, value );
-            case BOOTERCODE_TESTSET_STARTING:
-                return new TestsetStartingEvent( runMode, toReportEntry( args ) );
-            case BOOTERCODE_TESTSET_COMPLETED:
-                return new TestsetCompletedEvent( runMode, toReportEntry( args ) );
-            case BOOTERCODE_TEST_STARTING:
-                return new TestStartingEvent( runMode, toReportEntry( args ) );
-            case BOOTERCODE_TEST_SUCCEEDED:
-                return new TestSucceededEvent( runMode, toReportEntry( args ) );
-            case BOOTERCODE_TEST_FAILED:
-                return new TestFailedEvent( runMode, toReportEntry( args ) );
-            case BOOTERCODE_TEST_SKIPPED:
-                return new TestSkippedEvent( runMode, toReportEntry( args ) );
-            case BOOTERCODE_TEST_ERROR:
-                return new TestErrorEvent( runMode, toReportEntry( args ) );
-            case BOOTERCODE_TEST_ASSUMPTIONFAILURE:
-                return new TestAssumptionFailureEvent( runMode, toReportEntry( args ) );
-            default:
-                throw new IllegalArgumentException( "Missing a branch for the event type " + eventType );
-        }
-    }
-
-    private static void printCorruptedStream( Memento memento )
-    {
-        ByteBuffer bb = memento.bb;
-        if ( bb.hasRemaining() )
-        {
-            int bytesToWrite = bb.remaining();
-            memento.line.write( bb, bb.position(), bytesToWrite );
-            bb.position( bb.position() + bytesToWrite );
-        }
-    }
-
-    /**
-     * Print the last string which has not been finished by a new line character.
-     *
-     * @param memento current memento object
-     */
-    private static void printRemainingStream( Memento memento )
-    {
-        printCorruptedStream( memento );
-        memento.line.printExistingLine();
-        memento.line.count = 0;
-    }
-
-    @Nonnull
-    private static TestSetReportEntry toReportEntry( List<Object> args )
-    {
-        // ReportEntry:
-        String source = (String) args.get( 0 );
-        String sourceText = (String) args.get( 1 );
-        String name = (String) args.get( 2 );
-        String nameText = (String) args.get( 3 );
-        String group = (String) args.get( 4 );
-        String message = (String) args.get( 5 );
-        Integer timeElapsed = (Integer) args.get( 6 );
-        // StackTraceWriter:
-        String traceMessage = (String) args.get( 7 );
-        String smartTrimmedStackTrace = (String) args.get( 8 );
-        String stackTrace = (String) args.get( 9 );
-        return newReportEntry( source, sourceText, name, nameText, group, message, timeElapsed,
-            traceMessage, smartTrimmedStackTrace, stackTrace );
-    }
-
-    private static StackTraceWriter toStackTraceWriter( List<Object> args )
-    {
-        String traceMessage = (String) args.get( 0 );
-        String smartTrimmedStackTrace = (String) args.get( 1 );
-        String stackTrace = (String) args.get( 2 );
-        return toTrace( traceMessage, smartTrimmedStackTrace, stackTrace );
-    }
-
-    private static StackTraceWriter toTrace( String traceMessage, String smartTrimmedStackTrace, String stackTrace )
-    {
-        boolean exists = traceMessage != null || stackTrace != null || smartTrimmedStackTrace != null;
-        return exists ? new DeserializedStacktraceWriter( traceMessage, smartTrimmedStackTrace, stackTrace ) : null;
-    }
-
-    static TestSetReportEntry newReportEntry( // ReportEntry:
-                                              String source, String sourceText, String name,
-                                              String nameText, String group, String message,
-                                              Integer timeElapsed,
-                                              // StackTraceWriter:
-                                              String traceMessage,
-                                              String smartTrimmedStackTrace, String stackTrace )
-        throws NumberFormatException
-    {
-        StackTraceWriter stackTraceWriter = toTrace( traceMessage, smartTrimmedStackTrace, stackTrace );
-        return reportEntry( source, sourceText, name, nameText, group, stackTraceWriter, timeElapsed, message,
-            Collections.<String, String>emptyMap() );
-    }
-
-    private static boolean isJvmError( String line )
-    {
-        String lineLower = line.toLowerCase();
-        for ( String errorPattern : JVM_ERROR_PATTERNS )
+              CountdownCloseable c = countdownCloseable;
+              EventDecoder eventDecoder = decoder )
         {
-            if ( lineLower.contains( errorPattern ) )
-            {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private static int decodeString( @Nonnull CharsetDecoder decoder, @Nonnull ByteBuffer input,
-                                     @Nonnull CharBuffer output, @Nonnegative int bytesToDecode,
-                                     boolean endOfInput, @Nonnegative int errorStreamFrom )
-        throws MalformedFrameException
-    {
-        int limit = input.limit();
-        input.limit( input.position() + bytesToDecode );
-
-        CoderResult result = decoder.decode( input, output, endOfInput );
-        if ( result.isError() || result.isMalformed() )
-        {
-            throw new MalformedFrameException( errorStreamFrom, input.position() );
-        }
-        
-        int decodedBytes = bytesToDecode - input.remaining();
-        input.limit( limit );
-        return decodedBytes;
-    }
-
-    String readString( @Nonnull final Memento memento, @Nonnegative final int totalBytes )
-        throws IOException, MalformedFrameException
-    {
-        memento.getDecoder().reset();
-        final CharBuffer output = memento.cb;
-        output.clear();
-        final ByteBuffer input = memento.bb;
-        final List<String> strings = new ArrayList<>();
-        int countDecodedBytes = 0;
-        for ( boolean endOfInput = false; !endOfInput; )
-        {
-            final int bytesToRead = totalBytes - countDecodedBytes;
-            read( memento, bytesToRead - input.remaining() );
-            int bytesToDecode = min( input.remaining(), bytesToRead );
-            final boolean isLastChunk = bytesToDecode == bytesToRead;
-            endOfInput = countDecodedBytes + bytesToDecode >= totalBytes;
+            Memento memento = eventDecoder.new Memento();
             do
             {
-                boolean endOfChunk = output.remaining() >= bytesToRead;
-                boolean endOfOutput = isLastChunk && endOfChunk;
-                int readInputBytes = decodeString( memento.getDecoder(), input, output, bytesToDecode, endOfOutput,
-                    memento.line.positionByteBuffer );
-                bytesToDecode -= readInputBytes;
-                countDecodedBytes += readInputBytes;
-            }
-            while ( isLastChunk && bytesToDecode > 0 && output.hasRemaining() );
-
-            if ( isLastChunk || !output.hasRemaining() )
-            {
-                strings.add( output.flip().toString() );
-                output.clear();
-            }
-        }
-
-        memento.getDecoder().reset();
-        output.clear();
-
-        return toString( strings );
-    }
-
-    protected byte readByte( Memento memento ) throws IOException, MalformedFrameException
-    {
-        read( memento, BYTE_LENGTH + DELIMITER_LENGTH );
-        byte b = memento.bb.get();
-        checkDelimiter( memento );
-        return b;
-    }
-
-    protected int readInt( Memento memento ) throws IOException, MalformedFrameException
-    {
-        read( memento, INT_LENGTH + DELIMITER_LENGTH );
-        int i = memento.bb.getInt();
-        checkDelimiter( memento );
-        return i;
-    }
-
-    protected Integer readInteger( Memento memento ) throws IOException, MalformedFrameException
-    {
-        read( memento, BYTE_LENGTH );
-        boolean isNullObject = memento.bb.get() == 0;
-        if ( isNullObject )
-        {
-            read( memento, DELIMITER_LENGTH );
-            checkDelimiter( memento );
-            return null;
-        }
-        return readInt( memento );
-    }
-
-    private static String toString( List<String> strings )
-    {
-        if ( strings.size() == 1 )
-        {
-            return strings.get( 0 );
-        }
-        StringBuilder concatenated = new StringBuilder( strings.size() * BUFFER_SIZE );
-        for ( String s : strings )
-        {
-            concatenated.append( s );
-        }
-        return concatenated.toString();
-    }
-
-    static Map<Segment, ForkedProcessEventType> mapEventTypes()
-    {
-        Map<Segment, ForkedProcessEventType> map = new HashMap<>();
-        for ( ForkedProcessEventType e : ForkedProcessEventType.values() )
-        {
-            byte[] array = e.getOpcode().getBytes( US_ASCII );
-            map.put( new Segment( array, 0, array.length ), e );
-        }
-        return map;
-    }
-
-    static Map<Segment, RunMode> mapRunModes()
-    {
-        Map<Segment, RunMode> map = new HashMap<>();
-        for ( RunMode e : RunMode.values() )
-        {
-            byte[] array = e.geRunmode().getBytes( US_ASCII );
-            map.put( new Segment( array, 0, array.length ), e );
-        }
-        return map;
-    }
-
-    enum StreamReadStatus
-    {
-        UNDERFLOW,
-        OVERFLOW,
-        EOF
-    }
-
-    enum SegmentType
-    {
-        RUN_MODE,
-        STRING_ENCODING,
-        DATA_STRING,
-        DATA_INT,
-        END_OF_FRAME
-    }
-
-    /**
-     * This class avoids locking which gains the performance of this decoder.
-     */
-    private class BufferedStream
-    {
-        private byte[] buffer;
-        private int count;
-        private int positionByteBuffer;
-        private boolean isNewLine;
-
-        BufferedStream( int capacity )
-        {
-            this.buffer = new byte[capacity];
-        }
-
-        void write( ByteBuffer bb, int position, int length )
-        {
-            ensureCapacity( length );
-            byte[] array = bb.array();
-            int pos = bb.arrayOffset() + position;
-            while ( length-- > 0 )
-            {
-                positionByteBuffer++;
-                byte b = array[pos++];
-                if ( b == '\r' || b == '\n' )
+                Event event = eventDecoder.decode( memento );
+                if ( event != null && !disabled )
                 {
-                    if ( !isNewLine )
-                    {
-                        printExistingLine();
-                        count = 0;
-                    }
-                    isNewLine = true;
-                }
-                else
-                {
-                    buffer[count++] = b;
-                    isNewLine = false;
+                    eventHandler.handleEvent( event );
                 }
             }
+            while ( true );
         }
-
-        private boolean isEmpty()
+        catch ( EOFException e )
         {
-            return count == 0;
-        }
-
-        @Override
-        public String toString()
-        {
-            return new String( buffer, 0, count, DEFAULT_STREAM_ENCODING );
-        }
-
-        private void ensureCapacity( int addCapacity )
-        {
-            int oldCapacity = buffer.length;
-            int exactCapacity = count + addCapacity;
-            if ( exactCapacity < 0 )
-            {
-                throw new OutOfMemoryError();
-            }
-
-            if ( oldCapacity < exactCapacity )
-            {
-                int newCapacity = oldCapacity << 1;
-                buffer = Arrays.copyOf( buffer, max( newCapacity, exactCapacity ) );
-            }
+            //
         }
-
-        void printExistingLine()
-        {
-            if ( isEmpty() )
-            {
-                return;
-            }
-
-            String s = toString();
-            if ( s == null )
-            {
-                return;
-            }
-
-            ConsoleLogger logger = arguments.getConsoleLogger();
-            if ( s.contains( PRINTABLE_JVM_NATIVE_STREAM ) )
-            {
-                if ( logger.isDebugEnabled() )
-                {
-                    logger.debug( s );
-                }
-                else if ( logger.isInfoEnabled() )
-                {
-                    logger.info( s );
-                }
-                else
-                {
-                    // In case of debugging forked JVM, see PRINTABLE_JVM_NATIVE_STREAM.
-                    System.out.println( s );
-                }
-            }
-            else
-            {
-                if ( isJvmError( s ) )
-                {
-                    logger.error( s );
-                }
-                else if ( logger.isDebugEnabled() )
-                {
-                    logger.debug( s );
-                }
-
-                String msg = "Corrupted STDOUT by directly writing to native stream in forked JVM "
-                    + arguments.getForkChannelId() + ".";
-                File dumpFile = arguments.dumpStreamText( msg + " Stream '" + s + "'." );
-                arguments.logWarningAtEnd( msg + " See FAQ web page and the dump file " + dumpFile.getAbsolutePath() );
-            }
-        }
-    }
-
-    class Memento
-    {
-        private final CharsetDecoder defaultDecoder;
-        private CharsetDecoder currentDecoder;
-        final BufferedStream line = new BufferedStream( 32 );
-        final List<Object> data = new ArrayList<>();
-        final CharBuffer cb = CharBuffer.allocate( BUFFER_SIZE );
-        final ByteBuffer bb = ByteBuffer.allocate( BUFFER_SIZE );
-
-        Memento()
-        {
-            defaultDecoder = DEFAULT_STREAM_ENCODING.newDecoder()
-                .onMalformedInput( REPLACE )
-                .onUnmappableCharacter( REPLACE );
-        }
-
-        void reset()
-        {
-            currentDecoder = null;
-            data.clear();
-        }
-
-        CharsetDecoder getDecoder()
-        {
-            return currentDecoder == null ? defaultDecoder : currentDecoder;
-        }
-
-        void setCharset( Charset charset )
+        catch ( IOException e )
         {
-            if ( charset.name().equals( defaultDecoder.charset().name() ) )
+            if ( !( e.getCause() instanceof InterruptedException ) )
             {
-                currentDecoder = defaultDecoder;
-            }
-            else
-            {
-                currentDecoder = charset.newDecoder()
-                    .onMalformedInput( REPLACE )
-                    .onUnmappableCharacter( REPLACE );
+                arguments.dumpStreamException( e );
             }
         }
     }
 
-    static class Segment
+    @Override
+    public void disable()
     {
-        private final byte[] array;
-        private final int fromIndex;
-        private final int length;
-        private final int hashCode;
-
-        Segment( byte[] array, int fromIndex, int length )
-        {
-            this.array = array;
-            this.fromIndex = fromIndex;
-            this.length = length;
-
-            int hashCode = 0;
-            int i = fromIndex;
-            for ( int loops = length >> 1; loops-- != 0; )
-            {
-                hashCode = 31 * hashCode + array[i++];
-                hashCode = 31 * hashCode + array[i++];
-            }
-            this.hashCode = i == fromIndex + length ? hashCode : 31 * hashCode + array[i];
-        }
-
-        @Override
-        public int hashCode()
-        {
-            return hashCode;
-        }
-
-        @Override
-        public boolean equals( Object obj )
-        {
-            if ( !( obj instanceof Segment ) )
-            {
-                return false;
-            }
-
-            Segment that = (Segment) obj;
-            if ( that.length != length )
-            {
-                return false;
-            }
-
-            for ( int i = 0; i < length; i++ )
-            {
-                if ( that.array[that.fromIndex + i] != array[fromIndex + i] )
-                {
-                    return false;
-                }
-            }
-            return true;
-        }
+        disabled = true;
     }
 
-    /**
-     *
-     */
-    static class MalformedFrameException extends Exception
+    @Override
+    public void close() throws IOException
     {
-        private final int readFrom;
-        private final int readTo;
-
-        MalformedFrameException( int readFrom, int readTo )
-        {
-            this.readFrom = readFrom;
-            this.readTo = readTo;
-        }
-
-        boolean hasValidPositions()
-        {
-            return readFrom != NO_POSITION && readTo != NO_POSITION && readTo - readFrom > 0;
-        }
+        channel.close();
     }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkChannel.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkChannel.java
index 2400f94..e18f77d 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkChannel.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkChannel.java
@@ -24,7 +24,7 @@ import org.apache.maven.surefire.extensions.CloseableDaemonThread;
 import org.apache.maven.surefire.extensions.CommandReader;
 import org.apache.maven.surefire.extensions.EventHandler;
 import org.apache.maven.surefire.extensions.ForkChannel;
-import org.apache.maven.surefire.extensions.ForkNodeArguments;
+import org.apache.maven.surefire.api.fork.ForkNodeArguments;
 import org.apache.maven.surefire.extensions.util.CountdownCloseable;
 
 import javax.annotation.Nonnull;
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java
index 29d79af..b4d4788 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java
@@ -20,7 +20,7 @@ package org.apache.maven.plugin.surefire.extensions;
  */
 
 import org.apache.maven.surefire.extensions.ForkChannel;
-import org.apache.maven.surefire.extensions.ForkNodeArguments;
+import org.apache.maven.surefire.api.fork.ForkNodeArguments;
 import org.apache.maven.surefire.extensions.ForkNodeFactory;
 
 import javax.annotation.Nonnull;
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/StreamFeeder.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/StreamFeeder.java
index 3269967..0dcf5ef 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/StreamFeeder.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/StreamFeeder.java
@@ -21,28 +21,15 @@ package org.apache.maven.plugin.surefire.extensions;
 
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.surefire.api.booter.Command;
-import org.apache.maven.surefire.api.booter.MasterProcessCommand;
 import org.apache.maven.surefire.extensions.CloseableDaemonThread;
 import org.apache.maven.surefire.extensions.CommandReader;
-import org.apache.maven.surefire.api.util.internal.ImmutableMap;
+import org.apache.maven.surefire.stream.CommandEncoder;
 
 import javax.annotation.Nonnull;
 import java.io.IOException;
-import java.nio.ByteBuffer;
 import java.nio.channels.ClosedChannelException;
 import java.nio.channels.NonWritableChannelException;
 import java.nio.channels.WritableByteChannel;
-import java.util.HashMap;
-import java.util.Map;
-
-import static java.nio.charset.StandardCharsets.US_ASCII;
-import static org.apache.maven.surefire.api.booter.MasterProcessCommand.BYE_ACK;
-import static org.apache.maven.surefire.api.booter.MasterProcessCommand.MAGIC_NUMBER;
-import static org.apache.maven.surefire.api.booter.MasterProcessCommand.NOOP;
-import static org.apache.maven.surefire.api.booter.MasterProcessCommand.RUN_CLASS;
-import static org.apache.maven.surefire.api.booter.MasterProcessCommand.SHUTDOWN;
-import static org.apache.maven.surefire.api.booter.MasterProcessCommand.SKIP_SINCE_NEXT_TEST;
-import static org.apache.maven.surefire.api.booter.MasterProcessCommand.TEST_SET_FINISHED;
 
 /**
  * Commands which are sent from plugin to the forked jvm.
@@ -59,8 +46,6 @@ import static org.apache.maven.surefire.api.booter.MasterProcessCommand.TEST_SET
  */
 public class StreamFeeder extends CloseableDaemonThread
 {
-    private static final Map<MasterProcessCommand, String> COMMAND_OPCODES = opcodesToStrings();
-
     private final WritableByteChannel channel;
     private final CommandReader commandReader;
     private final ConsoleLogger logger;
@@ -81,15 +66,35 @@ public class StreamFeeder extends CloseableDaemonThread
     @SuppressWarnings( "checkstyle:innerassignment" )
     public void run()
     {
-        try ( WritableByteChannel c = channel )
+        try ( CommandEncoder encoder = new CommandEncoder( channel ) )
         {
             for ( Command cmd; ( cmd = commandReader.readNextCommand() ) != null; )
             {
                 if ( !disabled )
                 {
-                    MasterProcessCommand cmdType = cmd.getCommandType();
-                    byte[] data = cmdType.hasDataType() ? encode( cmdType, cmd.getData() ) : encode( cmdType );
-                    c.write( ByteBuffer.wrap( data ) );
+                    switch ( cmd.getCommandType() )
+                    {
+                        case RUN_CLASS:
+                            encoder.sendRunClass( cmd.getData() );
+                            break;
+                        case TEST_SET_FINISHED:
+                            encoder.sendTestSetFinished();
+                            break;
+                        case SKIP_SINCE_NEXT_TEST:
+                            encoder.sendSkipSinceNextTest();
+                            break;
+                        case SHUTDOWN:
+                            encoder.sendShutdown( cmd.getData() );
+                            break;
+                        case NOOP:
+                            encoder.sendNoop();
+                            break;
+                        case BYE_ACK:
+                            encoder.sendByeAck();
+                            break;
+                        default:
+                            logger.error( "Unknown enum " + cmd.getCommandType().name() );
+                    }
                 }
             }
         }
@@ -122,82 +127,4 @@ public class StreamFeeder extends CloseableDaemonThread
     {
         channel.close();
     }
-
-    /**
-     * Public method for testing purposes.
-     *
-     * @param cmdType command type
-     * @param data data to encode
-     * @return command with data encoded to bytes
-     */
-    public static byte[] encode( MasterProcessCommand cmdType, String data )
-    {
-        if ( !cmdType.hasDataType() )
-        {
-            throw new IllegalArgumentException( "cannot use data without data type" );
-        }
-
-        if ( cmdType.getDataType() != String.class )
-        {
-            throw new IllegalArgumentException( "Data type can be only " + String.class );
-        }
-
-        return encode( COMMAND_OPCODES.get( cmdType ), data )
-            .toString()
-            .getBytes( US_ASCII );
-    }
-
-    /**
-     * Public method for testing purposes.
-     *
-     * @param cmdType command type
-     * @return command without data encoded to bytes
-     */
-    public static byte[] encode( MasterProcessCommand cmdType )
-    {
-        if ( cmdType.getDataType() != Void.class )
-        {
-            throw new IllegalArgumentException( "Data type can be only " + cmdType.getDataType() );
-        }
-
-        return encode( COMMAND_OPCODES.get( cmdType ), null )
-            .toString()
-            .getBytes( US_ASCII );
-    }
-
-    /**
-     * Encodes opcode and data.
-     *
-     * @param operation opcode
-     * @param data   data
-     * @return encoded command
-     */
-    private static StringBuilder encode( String operation, String data )
-    {
-        StringBuilder s = new StringBuilder( 128 )
-            .append( ':' )
-            .append( MAGIC_NUMBER )
-            .append( ':' )
-            .append( operation );
-
-        if ( data != null )
-        {
-            s.append( ':' )
-                .append( data );
-        }
-
-        return s.append( ':' );
-    }
-
-    private static Map<MasterProcessCommand, String> opcodesToStrings()
-    {
-        Map<MasterProcessCommand, String> opcodes = new HashMap<>();
-        opcodes.put( RUN_CLASS, "run-testclass" );
-        opcodes.put( TEST_SET_FINISHED, "testset-finished" );
-        opcodes.put( SKIP_SINCE_NEXT_TEST, "skip-since-next-test" );
-        opcodes.put( SHUTDOWN, "shutdown" );
-        opcodes.put( NOOP, "noop" );
-        opcodes.put( BYE_ACK, "bye-ack" );
-        return new ImmutableMap<>( opcodes );
-    }
 }
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 639944e..5c3381c 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
@@ -25,7 +25,7 @@ import org.apache.maven.surefire.extensions.CloseableDaemonThread;
 import org.apache.maven.surefire.extensions.CommandReader;
 import org.apache.maven.surefire.extensions.EventHandler;
 import org.apache.maven.surefire.extensions.ForkChannel;
-import org.apache.maven.surefire.extensions.ForkNodeArguments;
+import org.apache.maven.surefire.api.fork.ForkNodeArguments;
 import org.apache.maven.surefire.extensions.util.CountdownCloseable;
 import org.apache.maven.surefire.extensions.util.LineConsumerThread;
 
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkNodeFactory.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkNodeFactory.java
index 0b47784..f9c6d71 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkNodeFactory.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkNodeFactory.java
@@ -20,7 +20,7 @@ package org.apache.maven.plugin.surefire.extensions;
  */
 
 import org.apache.maven.surefire.extensions.ForkChannel;
-import org.apache.maven.surefire.extensions.ForkNodeArguments;
+import org.apache.maven.surefire.api.fork.ForkNodeArguments;
 import org.apache.maven.surefire.extensions.ForkNodeFactory;
 
 import javax.annotation.Nonnull;
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/surefire/stream/CommandEncoder.java b/maven-surefire-common/src/main/java/org/apache/maven/surefire/stream/CommandEncoder.java
new file mode 100644
index 0000000..81f9d77
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/surefire/stream/CommandEncoder.java
@@ -0,0 +1,148 @@
+package org.apache.maven.surefire.stream;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.api.booter.MasterProcessCommand;
+import org.apache.maven.surefire.api.stream.AbstractStreamEncoder;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.WritableByteChannel;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+
+import static org.apache.maven.surefire.api.booter.Constants.DEFAULT_STREAM_ENCODING;
+import static org.apache.maven.surefire.api.booter.Constants.DEFAULT_STREAM_ENCODING_BYTES;
+import static org.apache.maven.surefire.api.booter.Constants.MAGIC_NUMBER_FOR_COMMANDS_BYTES;
+import static org.apache.maven.surefire.api.booter.MasterProcessCommand.BYE_ACK;
+import static org.apache.maven.surefire.api.booter.MasterProcessCommand.NOOP;
+import static org.apache.maven.surefire.api.booter.MasterProcessCommand.RUN_CLASS;
+import static org.apache.maven.surefire.api.booter.MasterProcessCommand.SHUTDOWN;
+import static org.apache.maven.surefire.api.booter.MasterProcessCommand.SKIP_SINCE_NEXT_TEST;
+import static org.apache.maven.surefire.api.booter.MasterProcessCommand.TEST_SET_FINISHED;
+import static org.apache.maven.surefire.api.report.RunMode.NORMAL_RUN;
+
+/**
+ *
+ */
+public class CommandEncoder extends AbstractStreamEncoder<MasterProcessCommand> implements AutoCloseable
+{
+    private final WritableByteChannel out;
+
+    public CommandEncoder( WritableByteChannel out )
+    {
+        super( out );
+        this.out = out;
+    }
+
+    public void sendRunClass( String testClassName ) throws IOException
+    {
+        CharsetEncoder encoder = newCharsetEncoder();
+        int bufferMaxLength =
+            estimateBufferLength( RUN_CLASS.getOpcodeLength(), NORMAL_RUN, encoder, 0, testClassName );
+        ByteBuffer result = ByteBuffer.allocate( bufferMaxLength );
+        encode( encoder, result, RUN_CLASS, NORMAL_RUN, testClassName );
+        write( result, true );
+    }
+
+    public void sendTestSetFinished() throws IOException
+    {
+        int bufferMaxLength = estimateBufferLength( TEST_SET_FINISHED.getOpcodeLength(), null, null, 0 );
+        ByteBuffer result = ByteBuffer.allocate( bufferMaxLength );
+        encodeHeader( result, TEST_SET_FINISHED, null );
+        write( result, true );
+    }
+
+    public void sendSkipSinceNextTest() throws IOException
+    {
+        int bufferMaxLength = estimateBufferLength( SKIP_SINCE_NEXT_TEST.getOpcodeLength(), null, null, 0 );
+        ByteBuffer result = ByteBuffer.allocate( bufferMaxLength );
+        encodeHeader( result, SKIP_SINCE_NEXT_TEST, null );
+        write( result, true );
+    }
+
+    public void sendShutdown( String shutdownData ) throws IOException
+    {
+        CharsetEncoder encoder = newCharsetEncoder();
+        int bufferMaxLength =
+            estimateBufferLength( SHUTDOWN.getOpcodeLength(), null, encoder, 0, shutdownData );
+        ByteBuffer result = ByteBuffer.allocate( bufferMaxLength );
+        encode( encoder, result, SHUTDOWN, null, shutdownData );
+        write( result, true );
+    }
+
+    public void sendNoop() throws IOException
+    {
+        int bufferMaxLength = estimateBufferLength( NOOP.getOpcodeLength(), null, null, 0 );
+        ByteBuffer result = ByteBuffer.allocate( bufferMaxLength );
+        encodeHeader( result, NOOP, null );
+        write( result, true );
+    }
+
+    public void sendByeAck() throws IOException
+    {
+        int bufferMaxLength = estimateBufferLength( BYE_ACK.getOpcodeLength(), null, null, 0 );
+        ByteBuffer result = ByteBuffer.allocate( bufferMaxLength );
+        encodeHeader( result, BYE_ACK, null );
+        write( result, true );
+    }
+
+    @Nonnull
+    @Override
+    protected final byte[] getEncodedMagicNumber()
+    {
+        return MAGIC_NUMBER_FOR_COMMANDS_BYTES;
+    }
+
+    @Nonnull
+    @Override
+    protected final byte[] enumToByteArray( MasterProcessCommand masterProcessCommand )
+    {
+        return masterProcessCommand.getOpcodeBinary();
+    }
+
+    @Nonnull
+    @Override
+    protected final byte[] getEncodedCharsetName()
+    {
+        return DEFAULT_STREAM_ENCODING_BYTES;
+    }
+
+    @Nonnull
+    @Override
+    protected final Charset getCharset()
+    {
+        return DEFAULT_STREAM_ENCODING;
+    }
+
+    @Nonnull
+    @Override
+    protected final CharsetEncoder newCharsetEncoder()
+    {
+        return getCharset().newEncoder();
+    }
+
+    @Override
+    public void close() throws IOException
+    {
+        out.close();
+    }
+}
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/surefire/stream/EventDecoder.java b/maven-surefire-common/src/main/java/org/apache/maven/surefire/stream/EventDecoder.java
new file mode 100644
index 0000000..3698511
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/surefire/stream/EventDecoder.java
@@ -0,0 +1,468 @@
+package org.apache.maven.surefire.stream;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.plugin.surefire.booterclient.output.DeserializedStacktraceWriter;
+import org.apache.maven.surefire.api.booter.ForkedProcessEventType;
+import org.apache.maven.surefire.api.event.ConsoleDebugEvent;
+import org.apache.maven.surefire.api.event.ConsoleErrorEvent;
+import org.apache.maven.surefire.api.event.ConsoleInfoEvent;
+import org.apache.maven.surefire.api.event.ConsoleWarningEvent;
+import org.apache.maven.surefire.api.event.ControlByeEvent;
+import org.apache.maven.surefire.api.event.ControlNextTestEvent;
+import org.apache.maven.surefire.api.event.ControlStopOnNextTestEvent;
+import org.apache.maven.surefire.api.event.Event;
+import org.apache.maven.surefire.api.event.JvmExitErrorEvent;
+import org.apache.maven.surefire.api.event.StandardStreamErrEvent;
+import org.apache.maven.surefire.api.event.StandardStreamErrWithNewLineEvent;
+import org.apache.maven.surefire.api.event.StandardStreamOutEvent;
+import org.apache.maven.surefire.api.event.StandardStreamOutWithNewLineEvent;
+import org.apache.maven.surefire.api.event.SystemPropertyEvent;
+import org.apache.maven.surefire.api.event.TestAssumptionFailureEvent;
+import org.apache.maven.surefire.api.event.TestErrorEvent;
+import org.apache.maven.surefire.api.event.TestFailedEvent;
+import org.apache.maven.surefire.api.event.TestSkippedEvent;
+import org.apache.maven.surefire.api.event.TestStartingEvent;
+import org.apache.maven.surefire.api.event.TestSucceededEvent;
+import org.apache.maven.surefire.api.event.TestsetCompletedEvent;
+import org.apache.maven.surefire.api.event.TestsetStartingEvent;
+import org.apache.maven.surefire.api.fork.ForkNodeArguments;
+import org.apache.maven.surefire.api.report.RunMode;
+import org.apache.maven.surefire.api.report.StackTraceWriter;
+import org.apache.maven.surefire.api.report.TestSetReportEntry;
+import org.apache.maven.surefire.api.stream.AbstractStreamDecoder;
+import org.apache.maven.surefire.api.stream.SegmentType;
+
+import javax.annotation.Nonnull;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.channels.ReadableByteChannel;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.FutureTask;
+
+import static org.apache.maven.surefire.api.booter.Constants.MAGIC_NUMBER_FOR_EVENTS_BYTES;
+import static org.apache.maven.surefire.api.report.CategorizedReportEntry.reportEntry;
+import static org.apache.maven.surefire.api.stream.SegmentType.DATA_INTEGER;
+import static org.apache.maven.surefire.api.stream.SegmentType.DATA_STRING;
+import static org.apache.maven.surefire.api.stream.SegmentType.END_OF_FRAME;
+import static org.apache.maven.surefire.api.stream.SegmentType.RUN_MODE;
+import static org.apache.maven.surefire.api.stream.SegmentType.STRING_ENCODING;
+
+/**
+ *
+ */
+public class EventDecoder extends AbstractStreamDecoder<Event, ForkedProcessEventType, SegmentType>
+{
+    private static final int DEBUG_SINK_BUFFER_SIZE = 64 * 1024;
+    // due to have fast and thread-safe Map
+    private static final Map<Segment, ForkedProcessEventType> EVENT_TYPES = segmentsToEvents();
+    private static final Map<Segment, RunMode> RUN_MODES = segmentsToRunModes();
+
+    private static final SegmentType[] EVENT_WITHOUT_DATA = new SegmentType[] {
+        END_OF_FRAME
+    };
+
+    private static final SegmentType[] EVENT_WITH_ERROR_TRACE = new SegmentType[] {
+        STRING_ENCODING,
+        DATA_STRING,
+        DATA_STRING,
+        DATA_STRING,
+        END_OF_FRAME
+    };
+
+    private static final SegmentType[] EVENT_WITH_ONE_STRING = new SegmentType[] {
+        STRING_ENCODING,
+        DATA_STRING,
+        END_OF_FRAME
+    };
+
+    private static final SegmentType[] EVENT_WITH_RUNMODE_AND_ONE_STRING = new SegmentType[] {
+        RUN_MODE,
+        STRING_ENCODING,
+        DATA_STRING,
+        END_OF_FRAME
+    };
+
+    private static final SegmentType[] EVENT_WITH_RUNMODE_AND_TWO_STRINGS = new SegmentType[] {
+        RUN_MODE,
+        STRING_ENCODING,
+        DATA_STRING,
+        DATA_STRING,
+        END_OF_FRAME
+    };
+
+    private static final SegmentType[] EVENT_TEST_CONTROL = new SegmentType[] {
+        RUN_MODE,
+        STRING_ENCODING,
+        DATA_STRING,
+        DATA_STRING,
+        DATA_STRING,
+        DATA_STRING,
+        DATA_STRING,
+        DATA_STRING,
+        DATA_INTEGER,
+        DATA_STRING,
+        DATA_STRING,
+        DATA_STRING,
+        END_OF_FRAME
+    };
+
+    private static final int NO_POSITION = -1;
+
+    private final OutputStream debugSink;
+
+    public EventDecoder( @Nonnull ReadableByteChannel channel,
+                         @Nonnull ForkNodeArguments arguments )
+    {
+        super( channel, arguments, EVENT_TYPES );
+        debugSink = newDebugSink( arguments );
+    }
+
+    @Override
+    public Event decode( @Nonnull Memento memento ) throws IOException
+    {
+        try
+        {
+            ForkedProcessEventType eventType = readMessageType( memento );
+            if ( eventType == null )
+            {
+                throw new MalformedFrameException( memento.getLine().getPositionByteBuffer(),
+                    memento.getByteBuffer().position() );
+            }
+            RunMode runMode = null;
+            for ( SegmentType segmentType : nextSegmentType( eventType ) )
+            {
+                switch ( segmentType )
+                {
+                    case RUN_MODE:
+                        runMode = RUN_MODES.get( readSegment( memento ) );
+                        break;
+                    case STRING_ENCODING:
+                        memento.setCharset( readCharset( memento ) );
+                        break;
+                    case DATA_STRING:
+                        memento.getData().add( readString( memento ) );
+                        break;
+                    case DATA_INTEGER:
+                        memento.getData().add( readInteger( memento ) );
+                        break;
+                    case END_OF_FRAME:
+                        memento.getLine().setPositionByteBuffer( memento.getByteBuffer().position() );
+                        return toMessage( eventType, runMode, memento );
+                    default:
+                        memento.getLine().setPositionByteBuffer( NO_POSITION );
+                        getArguments()
+                            .dumpStreamText( "Unknown enum ("
+                                + SegmentType.class.getSimpleName()
+                                + ") "
+                                + segmentType );
+                }
+            }
+        }
+        catch ( MalformedFrameException e )
+        {
+            if ( e.hasValidPositions() )
+            {
+                int length = e.readTo() - e.readFrom();
+                memento.getLine().write( memento.getByteBuffer(), e.readFrom(), length );
+            }
+            return null;
+        }
+        catch ( RuntimeException e )
+        {
+            getArguments().dumpStreamException( e );
+            return null;
+        }
+        catch ( IOException e )
+        {
+            if ( !( e.getCause() instanceof InterruptedException ) )
+            {
+                printRemainingStream( memento );
+            }
+            throw e;
+        }
+        finally
+        {
+            memento.reset();
+        }
+
+        throw new IOException( "unreachable statement" );
+    }
+
+    @Nonnull
+    @Override
+    protected final byte[] getEncodedMagicNumber()
+    {
+        return MAGIC_NUMBER_FOR_EVENTS_BYTES;
+    }
+
+    @Override
+    @Nonnull
+    protected final SegmentType[] nextSegmentType( @Nonnull ForkedProcessEventType eventType )
+    {
+        switch ( eventType )
+        {
+            case BOOTERCODE_BYE:
+            case BOOTERCODE_STOP_ON_NEXT_TEST:
+            case BOOTERCODE_NEXT_TEST:
+                return EVENT_WITHOUT_DATA;
+            case BOOTERCODE_CONSOLE_ERROR:
+            case BOOTERCODE_JVM_EXIT_ERROR:
+                return EVENT_WITH_ERROR_TRACE;
+            case BOOTERCODE_CONSOLE_INFO:
+            case BOOTERCODE_CONSOLE_DEBUG:
+            case BOOTERCODE_CONSOLE_WARNING:
+                return EVENT_WITH_ONE_STRING;
+            case BOOTERCODE_STDOUT:
+            case BOOTERCODE_STDOUT_NEW_LINE:
+            case BOOTERCODE_STDERR:
+            case BOOTERCODE_STDERR_NEW_LINE:
+                return EVENT_WITH_RUNMODE_AND_ONE_STRING;
+            case BOOTERCODE_SYSPROPS:
+                return EVENT_WITH_RUNMODE_AND_TWO_STRINGS;
+            case BOOTERCODE_TESTSET_STARTING:
+            case BOOTERCODE_TESTSET_COMPLETED:
+            case BOOTERCODE_TEST_STARTING:
+            case BOOTERCODE_TEST_SUCCEEDED:
+            case BOOTERCODE_TEST_FAILED:
+            case BOOTERCODE_TEST_SKIPPED:
+            case BOOTERCODE_TEST_ERROR:
+            case BOOTERCODE_TEST_ASSUMPTIONFAILURE:
+                return EVENT_TEST_CONTROL;
+            default:
+                throw new IllegalArgumentException( "Unknown enum " + eventType );
+        }
+    }
+
+    @Override
+    @Nonnull
+    protected final Event toMessage( @Nonnull ForkedProcessEventType eventType,
+                                     RunMode runMode,
+                                     @Nonnull Memento memento )
+        throws MalformedFrameException
+    {
+        switch ( eventType )
+        {
+            case BOOTERCODE_BYE:
+                checkArguments( memento, 0 );
+                return new ControlByeEvent();
+            case BOOTERCODE_STOP_ON_NEXT_TEST:
+                checkArguments( memento, 0 );
+                return new ControlStopOnNextTestEvent();
+            case BOOTERCODE_NEXT_TEST:
+                checkArguments( memento, 0 );
+                return new ControlNextTestEvent();
+            case BOOTERCODE_CONSOLE_ERROR:
+                checkArguments( memento, 3 );
+                return new ConsoleErrorEvent( toStackTraceWriter( memento.getData() ) );
+            case BOOTERCODE_JVM_EXIT_ERROR:
+                checkArguments( memento, 3 );
+                return new JvmExitErrorEvent( toStackTraceWriter( memento.getData() ) );
+            case BOOTERCODE_CONSOLE_INFO:
+                checkArguments( memento, 1 );
+                return new ConsoleInfoEvent( (String) memento.getData().get( 0 ) );
+            case BOOTERCODE_CONSOLE_DEBUG:
+                checkArguments( memento, 1 );
+                return new ConsoleDebugEvent( (String) memento.getData().get( 0 ) );
+            case BOOTERCODE_CONSOLE_WARNING:
+                checkArguments( memento, 1 );
+                return new ConsoleWarningEvent( (String) memento.getData().get( 0 ) );
+            case BOOTERCODE_STDOUT:
+                checkArguments( runMode, memento, 1 );
+                return new StandardStreamOutEvent( runMode, (String) memento.getData().get( 0 ) );
+            case BOOTERCODE_STDOUT_NEW_LINE:
+                checkArguments( runMode, memento, 1 );
+                return new StandardStreamOutWithNewLineEvent( runMode, (String) memento.getData().get( 0 ) );
+            case BOOTERCODE_STDERR:
+                checkArguments( runMode, memento, 1 );
+                return new StandardStreamErrEvent( runMode, (String) memento.getData().get( 0 ) );
+            case BOOTERCODE_STDERR_NEW_LINE:
+                checkArguments( runMode, memento, 1 );
+                return new StandardStreamErrWithNewLineEvent( runMode, (String) memento.getData().get( 0 ) );
+            case BOOTERCODE_SYSPROPS:
+                checkArguments( runMode, memento, 2 );
+                String key = (String) memento.getData().get( 0 );
+                String value = (String) memento.getData().get( 1 );
+                return new SystemPropertyEvent( runMode, key, value );
+            case BOOTERCODE_TESTSET_STARTING:
+                checkArguments( runMode, memento, 10 );
+                return new TestsetStartingEvent( runMode, toReportEntry( memento.getData() ) );
+            case BOOTERCODE_TESTSET_COMPLETED:
+                checkArguments( runMode, memento, 10 );
+                return new TestsetCompletedEvent( runMode, toReportEntry( memento.getData() ) );
+            case BOOTERCODE_TEST_STARTING:
+                checkArguments( runMode, memento, 10 );
+                return new TestStartingEvent( runMode, toReportEntry( memento.getData() ) );
+            case BOOTERCODE_TEST_SUCCEEDED:
+                checkArguments( runMode, memento, 10 );
+                return new TestSucceededEvent( runMode, toReportEntry( memento.getData() ) );
+            case BOOTERCODE_TEST_FAILED:
+                checkArguments( runMode, memento, 10 );
+                return new TestFailedEvent( runMode, toReportEntry( memento.getData() ) );
+            case BOOTERCODE_TEST_SKIPPED:
+                checkArguments( runMode, memento, 10 );
+                return new TestSkippedEvent( runMode, toReportEntry( memento.getData() ) );
+            case BOOTERCODE_TEST_ERROR:
+                checkArguments( runMode, memento, 10 );
+                return new TestErrorEvent( runMode, toReportEntry( memento.getData() ) );
+            case BOOTERCODE_TEST_ASSUMPTIONFAILURE:
+                checkArguments( runMode, memento, 10 );
+                return new TestAssumptionFailureEvent( runMode, toReportEntry( memento.getData() ) );
+            default:
+                throw new IllegalArgumentException( "Missing a branch for the event type " + eventType );
+        }
+    }
+
+    @Nonnull
+    private static TestSetReportEntry toReportEntry( List<Object> args )
+    {
+        // ReportEntry:
+        String source = (String) args.get( 0 );
+        String sourceText = (String) args.get( 1 );
+        String name = (String) args.get( 2 );
+        String nameText = (String) args.get( 3 );
+        String group = (String) args.get( 4 );
+        String message = (String) args.get( 5 );
+        Integer timeElapsed = (Integer) args.get( 6 );
+        // StackTraceWriter:
+        String traceMessage = (String) args.get( 7 );
+        String smartTrimmedStackTrace = (String) args.get( 8 );
+        String stackTrace = (String) args.get( 9 );
+        return newReportEntry( source, sourceText, name, nameText, group, message, timeElapsed,
+            traceMessage, smartTrimmedStackTrace, stackTrace );
+    }
+
+    private static StackTraceWriter toStackTraceWriter( List<Object> args )
+    {
+        String traceMessage = (String) args.get( 0 );
+        String smartTrimmedStackTrace = (String) args.get( 1 );
+        String stackTrace = (String) args.get( 2 );
+        return toTrace( traceMessage, smartTrimmedStackTrace, stackTrace );
+    }
+
+    private static StackTraceWriter toTrace( String traceMessage, String smartTrimmedStackTrace, String stackTrace )
+    {
+        boolean exists = traceMessage != null || stackTrace != null || smartTrimmedStackTrace != null;
+        return exists ? new DeserializedStacktraceWriter( traceMessage, smartTrimmedStackTrace, stackTrace ) : null;
+    }
+
+    static TestSetReportEntry newReportEntry( // ReportEntry:
+                                              String source, String sourceText, String name,
+                                              String nameText, String group, String message,
+                                              Integer timeElapsed,
+                                              // StackTraceWriter:
+                                              String traceMessage,
+                                              String smartTrimmedStackTrace, String stackTrace )
+        throws NumberFormatException
+    {
+        StackTraceWriter stackTraceWriter = toTrace( traceMessage, smartTrimmedStackTrace, stackTrace );
+        return reportEntry( source, sourceText, name, nameText, group, stackTraceWriter, timeElapsed, message,
+            Collections.<String, String>emptyMap() );
+    }
+
+    private static Map<Segment, ForkedProcessEventType> segmentsToEvents()
+    {
+        Map<Segment, ForkedProcessEventType> events = new HashMap<>();
+        for ( ForkedProcessEventType event : ForkedProcessEventType.values() )
+        {
+            byte[] array = event.getOpcodeBinary();
+            events.put( new Segment( array, 0, array.length ), event );
+        }
+        return events;
+    }
+
+    private static Map<Segment, RunMode> segmentsToRunModes()
+    {
+        Map<Segment, RunMode> runModes = new HashMap<>();
+        for ( RunMode runMode : RunMode.values() )
+        {
+            byte[] array = runMode.getRunmodeBinary();
+            runModes.put( new Segment( array, 0, array.length ), runMode );
+        }
+        return runModes;
+    }
+
+    @Override
+    protected void debugStream( byte[] array, int position, int remaining )
+    {
+        if ( debugSink == null )
+        {
+            return;
+        }
+
+        try
+        {
+            debugSink.write( array, position, remaining );
+            debugSink.flush();
+        }
+        catch ( IOException e )
+        {
+            // logger file was deleted
+            // System.out is already used by the stream in this decoder
+        }
+    }
+
+    private OutputStream newDebugSink( ForkNodeArguments arguments )
+    {
+        final File sink = arguments.getEventStreamBinaryFile();
+        if ( sink == null )
+        {
+            return null;
+        }
+
+        try
+        {
+            OutputStream fos = new FileOutputStream( sink, true );
+            final OutputStream os = new BufferedOutputStream( fos, DEBUG_SINK_BUFFER_SIZE );
+            Runtime.getRuntime().addShutdownHook( new Thread( new FutureTask<>( new Callable<Void>()
+            {
+                @Override
+                public Void call() throws Exception
+                {
+                    os.close();
+                    return null;
+                }
+            } ) ) );
+            return os;
+        }
+        catch ( FileNotFoundException e )
+        {
+            return null;
+        }
+    }
+
+    @Override
+    public void close() throws IOException
+    {
+        // do NOT close the channel, it's std/out.
+        if ( debugSink != null )
+        {
+            debugSink.close();
+        }
+    }
+}
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkingRunListenerTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkingRunListenerTest.java
index a8eb91f..48eb35f 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkingRunListenerTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkingRunListenerTest.java
@@ -25,10 +25,10 @@ import org.apache.maven.plugin.surefire.booterclient.output.ForkClient;
 import org.apache.maven.plugin.surefire.extensions.EventConsumerThread;
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.surefire.api.booter.ForkingRunListener;
-import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder;
+import org.apache.maven.surefire.booter.spi.EventChannelEncoder;
 import org.apache.maven.surefire.api.event.Event;
 import org.apache.maven.surefire.extensions.EventHandler;
-import org.apache.maven.surefire.extensions.ForkNodeArguments;
+import org.apache.maven.surefire.api.fork.ForkNodeArguments;
 import org.apache.maven.surefire.extensions.util.CountdownCloseable;
 import org.apache.maven.surefire.api.report.CategorizedReportEntry;
 import org.apache.maven.surefire.api.report.ConsoleOutputReceiver;
@@ -258,11 +258,11 @@ public class ForkingRunListenerTest
         ReportEntry expected = createDefaultReportEntry();
         SimpleReportEntry secondExpected = createAnotherDefaultReportEntry();
 
-        new ForkingRunListener( new LegacyMasterProcessChannelEncoder( newBufferedChannel( printStream ) ), false )
+        new ForkingRunListener( new EventChannelEncoder( newBufferedChannel( printStream ) ), false )
                 .testStarting( expected );
 
         new ForkingRunListener(
-            new LegacyMasterProcessChannelEncoder( newBufferedChannel( anotherPrintStream ) ), false )
+            new EventChannelEncoder( newBufferedChannel( anotherPrintStream ) ), false )
                 .testSkipped( secondExpected );
 
         TestSetMockReporterFactory providerReporterFactory = new TestSetMockReporterFactory();
@@ -375,6 +375,18 @@ public class ForkingRunListenerTest
         {
             return !dumpStreamText.isEmpty() || !logWarningAtEnd.isEmpty();
         }
+
+        @Override
+        public File getEventStreamBinaryFile()
+        {
+            return null;
+        }
+
+        @Override
+        public File getCommandStreamBinaryFile()
+        {
+            return null;
+        }
     }
 
     private static class EH implements EventHandler<Event>
@@ -446,7 +458,7 @@ public class ForkingRunListenerTest
     private RunListener createForkingRunListener()
     {
         WritableBufferedByteChannel channel = (WritableBufferedByteChannel) newChannel( printStream );
-        return new ForkingRunListener( new LegacyMasterProcessChannelEncoder( channel ), false );
+        return new ForkingRunListener( new EventChannelEncoder( channel ), false );
     }
 
     private class StandardTestRun
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/MainClass.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/MainClass.java
index 3d58ade..845897a 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/MainClass.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/MainClass.java
@@ -35,7 +35,7 @@ public class MainClass
         else
         {
             System.out.println( ":maven-surefire-event:\u0003:bye:" );
-            String byeAck = ":maven-surefire-command:bye-ack:";
+            String byeAck = ":maven-surefire-command:\u0007:bye-ack:";
             byte[] cmd = new byte[byeAck.length()];
             int len = System.in.read( cmd );
             if ( len != -1 && new String( cmd, 0, len ).equals( byeAck ) )
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStreamBuilderTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStreamBuilderTest.java
index 3c68778..87bad45 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStreamBuilderTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStreamBuilderTest.java
@@ -20,9 +20,9 @@ package org.apache.maven.plugin.surefire.booterclient.lazytestprovider;
  */
 
 import org.apache.maven.surefire.api.booter.Command;
-import org.apache.maven.surefire.api.booter.MasterProcessCommand;
-import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelDecoder;
 import org.apache.maven.surefire.api.booter.MasterProcessChannelDecoder;
+import org.apache.maven.surefire.booter.ForkedNodeArg;
+import org.apache.maven.surefire.booter.spi.CommandChannelDecoder;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
@@ -33,17 +33,19 @@ import java.util.Iterator;
 import java.util.NoSuchElementException;
 
 import static java.nio.channels.Channels.newChannel;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStream.TestLessInputStreamBuilder;
 import static org.apache.maven.surefire.api.booter.Command.SKIP_SINCE_NEXT_TEST;
+import static org.apache.maven.surefire.api.booter.MasterProcessCommand.NOOP;
 import static org.apache.maven.surefire.api.booter.MasterProcessCommand.SHUTDOWN;
 import static org.apache.maven.surefire.api.booter.Shutdown.EXIT;
 import static org.apache.maven.surefire.api.booter.Shutdown.KILL;
-import static org.apache.maven.plugin.surefire.extensions.StreamFeeder.encode;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.notNullValue;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 /**
  * Testing cached and immediate commands in {@link TestLessInputStream}.
@@ -144,6 +146,7 @@ public class TestLessInputStreamBuilderTest
         {
             private byte[] buffer;
             private int idx;
+            private boolean isLastBuffer;
 
             @Override
             public int read() throws IOException
@@ -154,8 +157,20 @@ public class TestLessInputStreamBuilderTest
                     Command cmd = pluginIs.readNextCommand();
                     if ( cmd != null )
                     {
-                        MasterProcessCommand cmdType = cmd.getCommandType();
-                        buffer = cmdType.hasDataType() ? encode( cmdType, cmd.getData() ) : encode( cmdType );
+                        if ( cmd.getCommandType() == SHUTDOWN )
+                        {
+                            buffer = ( ":maven-surefire-command:\u0008:shutdown:\u0005:UTF-8:\u0000\u0000\u0000\u0004:"
+                                + cmd.toShutdownData().getParam() + ":" ).getBytes( UTF_8 );
+                        }
+                        else if ( cmd.getCommandType() == NOOP )
+                        {
+                            buffer = ":maven-surefire-command:\u0004:noop:".getBytes( UTF_8 );
+                            isLastBuffer = true;
+                        }
+                        else
+                        {
+                            fail();
+                        }
                     }
                 }
 
@@ -169,10 +184,16 @@ public class TestLessInputStreamBuilderTest
                     }
                     return b;
                 }
+
+                if ( isLastBuffer )
+                {
+                    return -1;
+                }
                 throw new IOException();
             }
         };
-        MasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( is ) );
+        MasterProcessChannelDecoder decoder =
+            new CommandChannelDecoder( newChannel( is ), new ForkedNodeArg( 1, false ) );
         builder.getImmediateCommands().shutdown( KILL );
         builder.getImmediateCommands().noop();
         Command bye = decoder.decode();
@@ -181,7 +202,7 @@ public class TestLessInputStreamBuilderTest
         assertThat( bye.getData(), is( KILL.name() ) );
         Command noop = decoder.decode();
         assertThat( noop, is( notNullValue() ) );
-        assertThat( noop.getCommandType(), is( MasterProcessCommand.NOOP ) );
+        assertThat( noop.getCommandType(), is( NOOP ) );
     }
 
     @Test( expected = UnsupportedOperationException.class )
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStreamTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStreamTest.java
index e0acb9f..29a5745 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStreamTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStreamTest.java
@@ -20,9 +20,9 @@ package org.apache.maven.plugin.surefire.booterclient.lazytestprovider;
  */
 
 import org.apache.maven.surefire.api.booter.Command;
-import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelDecoder;
-import org.apache.maven.plugin.surefire.extensions.StreamFeeder;
 import org.apache.maven.surefire.api.booter.MasterProcessChannelDecoder;
+import org.apache.maven.surefire.booter.ForkedNodeArg;
+import org.apache.maven.surefire.booter.spi.CommandChannelDecoder;
 import org.junit.Test;
 
 import java.io.IOException;
@@ -36,7 +36,8 @@ import java.util.concurrent.FutureTask;
 import java.util.concurrent.TimeUnit;
 
 import static java.nio.channels.Channels.newChannel;
-import static java.nio.charset.StandardCharsets.US_ASCII;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.maven.surefire.api.booter.Command.TEST_SET_FINISHED;
 import static org.apache.maven.surefire.api.booter.MasterProcessCommand.BYE_ACK;
 import static org.apache.maven.surefire.api.booter.MasterProcessCommand.NOOP;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -44,6 +45,7 @@ import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.notNullValue;
 import static org.hamcrest.Matchers.nullValue;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 /**
  * Asserts that this stream properly reads bytes from queue.
@@ -93,8 +95,7 @@ public class TestProvidingInputStreamTest
     public void finishedTestsetShouldNotBlock()
         throws IOException
     {
-        Queue<String> commands = new ArrayDeque<>();
-        final TestProvidingInputStream is = new TestProvidingInputStream( commands );
+        final TestProvidingInputStream is = new TestProvidingInputStream( new ArrayDeque<String>() );
         is.testSetFinished();
         new Thread( new Runnable()
         {
@@ -105,16 +106,12 @@ public class TestProvidingInputStreamTest
             }
         } ).start();
 
-        Command cmd = is.readNextCommand();
-        assertThat( cmd.getData(), is( nullValue() ) );
-        String stream = new String( StreamFeeder.encode( cmd.getCommandType() ), US_ASCII );
-
-        cmd = is.readNextCommand();
-        assertThat( cmd.getData(), is( nullValue() ) );
-        stream += new String( StreamFeeder.encode( cmd.getCommandType() ), US_ASCII );
-
-        assertThat( stream,
-            is( ":maven-surefire-command:testset-finished::maven-surefire-command:testset-finished:" ) );
+        for ( int i = 0; i < 2; i++ )
+        {
+            Command cmd = is.readNextCommand();
+            assertThat( cmd.getData(), is( nullValue() ) );
+            assertThat( cmd, is( TEST_SET_FINISHED ) );
+        }
 
         boolean emptyStream = isInputStreamEmpty( is );
 
@@ -162,7 +159,21 @@ public class TestProvidingInputStreamTest
                 {
                     idx = 0;
                     Command cmd = pluginIs.readNextCommand();
-                    buffer = cmd == null ? null : StreamFeeder.encode( cmd.getCommandType() );
+                    if ( cmd != null )
+                    {
+                        if ( cmd.getCommandType() == BYE_ACK )
+                        {
+                            buffer = ":maven-surefire-command:\u0007:bye-ack:".getBytes( UTF_8 );
+                        }
+                        else if ( cmd.getCommandType() == NOOP )
+                        {
+                            buffer = ":maven-surefire-command:\u0004:noop:".getBytes( UTF_8 );
+                        }
+                        else
+                        {
+                            fail();
+                        }
+                    }
                 }
 
                 if ( buffer != null )
@@ -178,7 +189,8 @@ public class TestProvidingInputStreamTest
                 throw new IOException();
             }
         };
-        MasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( is ) );
+        MasterProcessChannelDecoder decoder =
+            new CommandChannelDecoder( newChannel( is ), new ForkedNodeArg( 1, false ) );
         pluginIs.acknowledgeByeEventReceived();
         pluginIs.noop();
         Command bye = decoder.decode();
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClientTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClientTest.java
index 5d871a3..531bafb 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClientTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClientTest.java
@@ -52,7 +52,7 @@ import org.apache.maven.surefire.api.report.SimpleReportEntry;
 import org.apache.maven.surefire.api.report.StackTraceWriter;
 import org.apache.maven.surefire.api.report.TestSetReportEntry;
 import org.apache.maven.surefire.extensions.EventHandler;
-import org.apache.maven.surefire.extensions.ForkNodeArguments;
+import org.apache.maven.surefire.api.fork.ForkNodeArguments;
 import org.apache.maven.surefire.extensions.util.CountdownCloseable;
 import org.junit.Test;
 
@@ -302,7 +302,8 @@ public class ForkClientTest
         assertThat( logger.isDebugEnabledCalled )
             .isTrue();
 
-        String msg = "Corrupted STDOUT by directly writing to native stream in forked JVM 0. Stream 'unordered error'.";
+        String msg =
+            "Corrupted channel by directly writing to native stream in forked JVM 0. Stream 'unordered error'.";
         assertThat( arguments.dumpStreamText )
             .hasSize( 1 )
             .contains( msg );
@@ -310,7 +311,7 @@ public class ForkClientTest
         assertThat( arguments.logWarningAtEnd )
             .hasSize( 1 );
         assertThat( arguments.logWarningAtEnd.peek() )
-            .startsWith( "Corrupted STDOUT by directly writing to native stream in forked JVM 0. "
+            .startsWith( "Corrupted channel by directly writing to native stream in forked JVM 0. "
                 + "See FAQ web page and the dump file" );
     }
 
@@ -1883,6 +1884,18 @@ public class ForkClientTest
         {
             return !dumpStreamText.isEmpty() || !logWarningAtEnd.isEmpty();
         }
+
+        @Override
+        public File getEventStreamBinaryFile()
+        {
+            return null;
+        }
+
+        @Override
+        public File getCommandStreamBinaryFile()
+        {
+            return null;
+        }
     }
 
     /**
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 f3becd7..87bcca7 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
@@ -27,7 +27,7 @@ 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.api.fork.ForkNodeArguments;
 import org.apache.maven.surefire.extensions.util.CountdownCloseable;
 import org.junit.Rule;
 import org.junit.Test;
@@ -74,7 +74,7 @@ public class E2ETest
 
         final SurefireMasterProcessChannelProcessorFactory factory = new SurefireMasterProcessChannelProcessorFactory();
         factory.connect( connection );
-        final MasterProcessChannelEncoder encoder = factory.createEncoder();
+        final MasterProcessChannelEncoder encoder = factory.createEncoder( arguments );
 
         System.gc();
 
@@ -333,5 +333,17 @@ public class E2ETest
         {
             return logger;
         }
+
+        @Override
+        public File getEventStreamBinaryFile()
+        {
+            return null;
+        }
+
+        @Override
+        public File getCommandStreamBinaryFile()
+        {
+            return null;
+        }
     }
 }
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThreadTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThreadTest.java
index 63287df..9357080 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThreadTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThreadTest.java
@@ -19,424 +19,32 @@ package org.apache.maven.plugin.surefire.extensions;
  * under the License.
  */
 
-import org.apache.maven.plugin.surefire.extensions.EventConsumerThread.Memento;
-import org.apache.maven.plugin.surefire.extensions.EventConsumerThread.Segment;
-import org.apache.maven.plugin.surefire.extensions.EventConsumerThread.SegmentType;
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
-import org.apache.maven.surefire.api.booter.ForkedProcessEventType;
-import org.apache.maven.surefire.api.event.ConsoleDebugEvent;
-import org.apache.maven.surefire.api.event.ConsoleErrorEvent;
-import org.apache.maven.surefire.api.event.ConsoleInfoEvent;
-import org.apache.maven.surefire.api.event.ConsoleWarningEvent;
-import org.apache.maven.surefire.api.event.ControlByeEvent;
-import org.apache.maven.surefire.api.event.ControlNextTestEvent;
-import org.apache.maven.surefire.api.event.ControlStopOnNextTestEvent;
 import org.apache.maven.surefire.api.event.Event;
-import org.apache.maven.surefire.api.event.JvmExitErrorEvent;
-import org.apache.maven.surefire.api.event.StandardStreamErrEvent;
-import org.apache.maven.surefire.api.event.StandardStreamErrWithNewLineEvent;
-import org.apache.maven.surefire.api.event.StandardStreamOutEvent;
-import org.apache.maven.surefire.api.event.StandardStreamOutWithNewLineEvent;
-import org.apache.maven.surefire.api.event.SystemPropertyEvent;
-import org.apache.maven.surefire.api.event.TestAssumptionFailureEvent;
-import org.apache.maven.surefire.api.event.TestErrorEvent;
-import org.apache.maven.surefire.api.event.TestFailedEvent;
-import org.apache.maven.surefire.api.event.TestSkippedEvent;
-import org.apache.maven.surefire.api.event.TestStartingEvent;
-import org.apache.maven.surefire.api.event.TestSucceededEvent;
-import org.apache.maven.surefire.api.event.TestsetCompletedEvent;
-import org.apache.maven.surefire.api.event.TestsetStartingEvent;
-import org.apache.maven.surefire.api.report.RunMode;
+import org.apache.maven.surefire.api.fork.ForkNodeArguments;
 import org.apache.maven.surefire.extensions.EventHandler;
-import org.apache.maven.surefire.extensions.ForkNodeArguments;
 import org.apache.maven.surefire.extensions.util.CountdownCloseable;
-import org.fest.assertions.Condition;
 import org.junit.Test;
 
 import javax.annotation.Nonnull;
 import java.io.Closeable;
-import java.io.EOFException;
 import java.io.File;
-import java.math.BigInteger;
 import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
 import java.nio.channels.ReadableByteChannel;
-import java.nio.charset.CharsetDecoder;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import static java.lang.Math.min;
-import static java.nio.charset.CodingErrorAction.REPLACE;
-import static java.nio.charset.StandardCharsets.ISO_8859_1;
-import static java.nio.charset.StandardCharsets.US_ASCII;
 import static java.nio.charset.StandardCharsets.UTF_8;
-import static java.util.Arrays.asList;
 import static java.util.Arrays.copyOfRange;
-import static java.util.Collections.emptyList;
-import static java.util.Collections.singletonList;
-import static org.apache.maven.plugin.surefire.extensions.EventConsumerThread.SegmentType.DATA_INT;
-import static org.apache.maven.plugin.surefire.extensions.EventConsumerThread.SegmentType.DATA_STRING;
-import static org.apache.maven.plugin.surefire.extensions.EventConsumerThread.SegmentType.END_OF_FRAME;
-import static org.apache.maven.plugin.surefire.extensions.EventConsumerThread.SegmentType.RUN_MODE;
-import static org.apache.maven.plugin.surefire.extensions.EventConsumerThread.SegmentType.STRING_ENCODING;
-import static org.apache.maven.plugin.surefire.extensions.EventConsumerThread.mapEventTypes;
-import static org.apache.maven.plugin.surefire.extensions.EventConsumerThread.mapRunModes;
-import static org.apache.maven.plugin.surefire.extensions.EventConsumerThread.nextSegmentType;
-import static org.apache.maven.plugin.surefire.extensions.EventConsumerThread.toEvent;
-import static org.apache.maven.surefire.api.booter.Constants.DEFAULT_STREAM_ENCODING;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_BYE;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_DEBUG;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_ERROR;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_INFO;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_WARNING;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_JVM_EXIT_ERROR;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_NEXT_TEST;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDERR;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDERR_NEW_LINE;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDOUT;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDOUT_NEW_LINE;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STOP_ON_NEXT_TEST;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_SYSPROPS;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TESTSET_COMPLETED;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TESTSET_STARTING;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_ASSUMPTIONFAILURE;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_ERROR;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_FAILED;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_SKIPPED;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_STARTING;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_SUCCEEDED;
-import static org.apache.maven.surefire.api.report.RunMode.NORMAL_RUN;
-import static org.apache.maven.surefire.api.report.RunMode.RERUN_TEST_AFTER_FAILURE;
+import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.fest.assertions.Assertions.assertThat;
-import static org.powermock.reflect.Whitebox.invokeMethod;
 
 /**
- * The performance of "get( Integer )" is 13.5 nano seconds on i5/2.6GHz:
- * <pre>
- *     {@code
- *     TreeMap<Integer, ForkedProcessEventType> map = new TreeMap<>();
- *     map.get( hash );
- *     }
- * </pre>
  *
- * <br> The performance of getting event type by Segment is 33.7 nano seconds:
- * <pre>
- *     {@code
- *     Map<Segment, ForkedProcessEventType> map = new HashMap<>();
- *     byte[] array = ForkedProcessEventType.BOOTERCODE_STDOUT.getOpcode().getBytes( UTF_8 );
- *     map.get( new Segment( array, 0, array.length ) );
- *     }
- * </pre>
- *
- * <br> The performance of decoder:
- * <pre>
- *     {@code
- *     CharsetDecoder decoder = STREAM_ENCODING.newDecoder()
- *             .onMalformedInput( REPLACE )
- *             .onUnmappableCharacter( REPLACE );
- *     ByteBuffer buffer = ByteBuffer.wrap( ForkedProcessEventType.BOOTERCODE_STDOUT.getOpcode().getBytes( UTF_8 ) );
- *     CharBuffer chars = CharBuffer.allocate( 100 );
- *     decoder.reset().decode( buffer, chars, true );
- *
- *     String s = chars.flip().toString(); // 37 nanos = CharsetDecoder + toString
- *
- *     buffer.clear();
- *     chars.clear();
- *
- *     ForkedProcessEventType.byOpcode( s ); // 65 nanos = CharsetDecoder + toString + byOpcode
- *     }
- * </pre>
- *
- * <br> The performance of decoding 100 bytes via CharacterDecoder - 71 nano seconds:
- * <pre>
- *     {@code
- *     decoder.reset()
- *         .decode( buffer, chars, true ); // CharsetDecoder 71 nanos
- *     chars.flip().toString(); // CharsetDecoder + toString = 91 nanos
- *     }
- * </pre>
- *
- * <br> The performance of a pure string creation (instead of decoder) - 31.5 nano seconds:
- * <pre>
- *     {@code
- *     byte[] b = {};
- *     new String( b, UTF_8 );
- *     }
- * </pre>
- *
- * <br> The performance of CharsetDecoder with empty ByteBuffer:
- * <pre>
- *     {@code
- *     CharsetDecoder + ByteBuffer.allocate( 0 ) makes 11.5 nanos
- *     CharsetDecoder + ByteBuffer.allocate( 0 ) + toString() makes 16.1 nanos
- *     }
- * </pre>
  */
-@SuppressWarnings( "checkstyle:magicnumber" )
 public class EventConsumerThreadTest
 {
-    private static final CountdownCloseable COUNTDOWN_CLOSEABLE = new CountdownCloseable( new MockCloseable(), 0 );
-
-    private static final String PATTERN1 =
-        "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789";
-
-    private static final String PATTERN2 = "€ab©c";
-
-    private static final byte[] PATTERN2_BYTES =
-        new byte[]{(byte) -30, (byte) -126, (byte) -84, 'a', 'b', (byte) 0xc2, (byte) 0xa9, 'c'};
-
-    @Test
-    public void shouldDecodeHappyCase() throws Exception
-    {
-        CharsetDecoder decoder = UTF_8.newDecoder()
-            .onMalformedInput( REPLACE )
-            .onUnmappableCharacter( REPLACE );
-        ByteBuffer input = ByteBuffer.allocate( 1024 );
-        input.put( PATTERN2_BYTES )
-            .flip();
-        int bytesToDecode = PATTERN2_BYTES.length;
-        CharBuffer output = CharBuffer.allocate( 1024 );
-        int readBytes =
-            invokeMethod( EventConsumerThread.class, "decodeString", decoder, input, output, bytesToDecode, true, 0 );
-
-        assertThat( readBytes )
-            .isEqualTo( bytesToDecode );
-
-        assertThat( output.flip().toString() )
-            .isEqualTo( PATTERN2 );
-    }
-
-    @Test
-    public void shouldDecodeShifted() throws Exception
-    {
-        CharsetDecoder decoder = UTF_8.newDecoder()
-            .onMalformedInput( REPLACE )
-            .onUnmappableCharacter( REPLACE );
-        ByteBuffer input = ByteBuffer.allocate( 1024 );
-        input.put( PATTERN1.getBytes( UTF_8 ) )
-            .put( 90, (byte) 'A' )
-            .put( 91, (byte) 'B' )
-            .put( 92, (byte) 'C' )
-            .position( 90 );
-        CharBuffer output = CharBuffer.allocate( 1024 );
-        int readBytes =
-            invokeMethod( EventConsumerThread.class, "decodeString", decoder, input, output, 2, true, 0 );
-
-        assertThat( readBytes )
-            .isEqualTo( 2 );
-
-        assertThat( output.flip().toString() )
-            .isEqualTo( "AB" );
-    }
-
-    @Test( expected = IllegalArgumentException.class )
-    public void shouldNotDecode() throws Exception
-    {
-        CharsetDecoder decoder = UTF_8.newDecoder();
-        ByteBuffer input = ByteBuffer.allocate( 100 );
-        int bytesToDecode = 101;
-        CharBuffer output = CharBuffer.allocate( 1000 );
-        invokeMethod( EventConsumerThread.class, "decodeString", decoder, input, output, bytesToDecode, true, 0 );
-    }
-
-    @Test
-    public void shouldReadInt() throws Exception
-    {
-        Channel channel = new Channel( new byte[] {0x01, 0x02, 0x03, 0x04, ':'}, 1 );
-
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-
-        Memento memento = thread.new Memento();
-        memento.bb.position( 0 ).limit( 0 );
-        assertThat( thread.readInt( memento ) )
-            .isEqualTo( new BigInteger( new byte[] {0x01, 0x02, 0x03, 0x04} ).intValue() );
-    }
-
-    @Test
-    public void shouldReadInteger() throws Exception
-    {
-        Channel channel = new Channel( new byte[] {(byte) 0xff, 0x01, 0x02, 0x03, 0x04, ':'}, 1 );
-
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-
-        Memento memento = thread.new Memento();
-        memento.bb.position( 0 ).limit( 0 );
-        assertThat( thread.readInteger( memento ) )
-            .isEqualTo( new BigInteger( new byte[] {0x01, 0x02, 0x03, 0x04} ).intValue() );
-    }
-
-    @Test
-    public void shouldReadNullInteger() throws Exception
-    {
-        Channel channel = new Channel( new byte[] {(byte) 0x00, ':'}, 1 );
-
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-
-        Memento memento = thread.new Memento();
-        memento.bb.position( 0 ).limit( 0 );
-        assertThat( thread.readInteger( memento ) )
-            .isNull();
-    }
-
-    @Test( expected = EOFException.class )
-    public void shouldNotReadString() throws Exception
-    {
-        Channel channel = new Channel( PATTERN1.getBytes(), PATTERN1.length() );
-        channel.read( ByteBuffer.allocate( 100 ) );
-
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-
-        Memento memento = thread.new Memento();
-        memento.bb.position( 0 ).limit( 0 );
-        thread.readString( memento, 10 );
-    }
-
-    @Test
-    public void shouldReadString() throws Exception
-    {
-        Channel channel = new Channel( PATTERN1.getBytes(), PATTERN1.length() );
-
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-
-        Memento memento = thread.new Memento();
-        memento.bb.position( 0 ).limit( 0 );
-        String s = thread.readString( memento, 10 );
-        assertThat( s )
-            .isEqualTo( "0123456789" );
-    }
-
-    @Test
-    public void shouldReadStringShiftedBuffer() throws Exception
-    {
-        StringBuilder s = new StringBuilder( 1100 );
-        for ( int i = 0; i < 11; i++ )
-        {
-            s.append( PATTERN1 );
-        }
-
-        Channel channel = new Channel( s.toString().getBytes( UTF_8 ), s.length() );
-
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-
-        Memento memento = thread.new Memento();
-        // whatever position will be compacted to 0
-        memento.bb.position( 974 ).limit( 974 );
-        assertThat( thread.readString( memento, PATTERN1.length() + 3 ) )
-            .isEqualTo( PATTERN1 + "012" );
-    }
-
-    @Test
-    public void shouldReadStringShiftedInput() throws Exception
-    {
-        StringBuilder s = new StringBuilder( 1100 );
-        for ( int i = 0; i < 11; i++ )
-        {
-            s.append( PATTERN1 );
-        }
-
-        Channel channel = new Channel( s.toString().getBytes( UTF_8 ), s.length() );
-        channel.read( ByteBuffer.allocate( 997 ) );
-
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-
-        Memento memento = thread.new Memento();
-        memento.bb.limit( 0 );
-        assertThat( thread.readString( memento, PATTERN1.length() ) )
-            .isEqualTo( "789" + PATTERN1.substring( 0, 97 ) );
-    }
-
-    @Test
-    public void shouldReadMultipleStringsAndShiftedInput() throws Exception
-    {
-        StringBuilder s = new StringBuilder( 5000 );
-
-        for ( int i = 0; i < 50; i++ )
-        {
-            s.append( PATTERN1 );
-        }
-
-        Channel channel = new Channel( s.toString().getBytes( UTF_8 ), s.length() );
-        channel.read( ByteBuffer.allocate( 1997 ) );
-
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-
-        Memento memento = thread.new Memento();
-        // whatever position will be compacted to 0
-        memento.bb.position( 974 ).limit( 974 );
-
-        StringBuilder expected = new StringBuilder( "789" );
-        for ( int i = 0; i < 11; i++ )
-        {
-            expected.append( PATTERN1 );
-        }
-        expected.setLength( 1100 );
-        assertThat( thread.readString( memento, 1100 ) )
-            .isEqualTo( expected.toString() );
-    }
-
-    @Test
-    public void shouldDecode3BytesEncodedSymbol() throws Exception
-    {
-        byte[] encodedSymbol = new byte[] {(byte) -30, (byte) -126, (byte) -84};
-        int countSymbols = 1024;
-        byte[] input = new byte[encodedSymbol.length * countSymbols];
-        for ( int i = 0; i < countSymbols; i++ )
-        {
-            System.arraycopy( encodedSymbol, 0, input, encodedSymbol.length * i, encodedSymbol.length );
-        }
-
-        Channel channel = new Channel( input, 2 );
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-        Memento memento = thread.new Memento();
-        memento.bb.position( 0 ).limit( 0 );
-        String decodedOutput = thread.readString( memento, input.length );
-
-        assertThat( decodedOutput )
-            .isEqualTo( new String( input, 0, input.length, UTF_8 ) );
-    }
-
-    @Test
-    public void shouldDecode100Bytes()
-    {
-        CharsetDecoder decoder = DEFAULT_STREAM_ENCODING.newDecoder()
-            .onMalformedInput( REPLACE )
-            .onUnmappableCharacter( REPLACE );
-        // empty stream: CharsetDecoder + ByteBuffer.allocate( 0 ) makes 11.5 nanos
-        // empty stream: CharsetDecoder + ByteBuffer.allocate( 0 ) + toString() makes 16.1 nanos
-        ByteBuffer buffer = ByteBuffer.wrap( PATTERN1.getBytes( UTF_8 ) );
-        CharBuffer chars = CharBuffer.allocate( 100 );
-        /* uncomment this section for a proper measurement of the exec time
-        TimeUnit.SECONDS.sleep( 2 );
-        System.gc();
-        TimeUnit.SECONDS.sleep( 5 );
-        */
-        String s = null;
-        long l1 = System.currentTimeMillis();
-        for ( int i = 0; i < 10_000_000; i++ )
-        {
-            decoder.reset()
-                .decode( buffer, chars, true ); // CharsetDecoder 71 nanos
-            s = chars.flip().toString(); // CharsetDecoder + toString = 91 nanos
-            buffer.clear();
-            chars.clear();
-        }
-        long l2 = System.currentTimeMillis();
-        System.out.println( "decoded 100 bytes within " + ( l2 - l1 ) + " millis (10 million cycles)" );
-        assertThat( s )
-            .isEqualTo( PATTERN1 );
-    }
-
+    @SuppressWarnings( "checkstyle:magicnumber" )
     @Test( timeout = 60_000L )
     public void performanceTest() throws Exception
     {
@@ -477,32 +85,36 @@ public class EventConsumerThreadTest
 
         event.flip();
         byte[] frame = copyOfRange( event.array(), event.arrayOffset(), event.arrayOffset() + event.remaining() );
-        ReadableByteChannel channel = new Channel( frame, 1024 )
+        ReadableByteChannel channel = new Channel( frame, 100 )
         {
             private int countRounds;
 
             @Override
-            public int read( ByteBuffer dst )
+            public synchronized int read( ByteBuffer dst )
             {
-                int length = super.read( dst );
-                if ( length == -1 && countRounds < totalCalls )
+                if ( countRounds == totalCalls )
+                {
+                    return -1;
+                }
+
+                if ( remaining() == 0 )
                 {
-                    i = 0;
-                    length = super.read( dst );
                     countRounds++;
+                    i = 0;
                 }
-                return length;
+
+                return super.read( dst );
             }
         };
 
         EventConsumerThread thread = new EventConsumerThread( "t", channel, handler,
             new CountdownCloseable( new MockCloseable(), 1 ), new MockForkNodeArguments() );
 
-        TimeUnit.SECONDS.sleep( 2 );
+        SECONDS.sleep( 2 );
         System.gc();
-        TimeUnit.SECONDS.sleep( 5 );
+        SECONDS.sleep( 5 );
 
-        System.out.println( "Staring the event thread..." );
+        System.out.println( "Starting the event thread..." );
 
         thread.start();
         thread.join();
@@ -510,596 +122,12 @@ public class EventConsumerThreadTest
         long execTime = finishedAt[0] - staredAt[0];
         System.out.println( execTime );
 
-        // 0.6 seconds while using the encoder/decoder
+        // 0.6 seconds while using the encoder/decoder for 10 million messages
         assertThat( execTime )
-            .describedAs( "The performance test should assert 1.0s of read time. "
-                + "The limit 3.6s guarantees that the read time does not exceed this limit on overloaded CPU." )
+            .describedAs( "The performance test should assert 0.75s of read time. "
+                + "The limit 3.65s guarantees that the read time does not exceed this limit on overloaded CPU." )
             .isPositive()
-            .isLessThanOrEqualTo( 3_600L );
-    }
-
-    @Test
-    public void shouldReadEventType() throws Exception
-    {
-        Map<Segment, ForkedProcessEventType> eventTypes = mapEventTypes();
-        assertThat( eventTypes )
-            .hasSize( ForkedProcessEventType.values().length );
-
-        byte[] stream = ":maven-surefire-event:\u000E:std-out-stream:".getBytes( UTF_8 );
-        Channel channel = new Channel( stream, 1 );
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-
-        Memento memento = thread.new Memento();
-        memento.bb.position( 0 ).limit( 0 );
-        memento.setCharset( UTF_8 );
-
-        ForkedProcessEventType eventType = thread.readEventType( eventTypes, memento );
-        assertThat( eventType )
-            .isEqualTo( BOOTERCODE_STDOUT );
-    }
-
-    @Test( expected = EOFException.class )
-    public void shouldEventTypeReachedEndOfStream() throws Exception
-    {
-        Map<Segment, ForkedProcessEventType> eventTypes = mapEventTypes();
-        assertThat( eventTypes )
-            .hasSize( ForkedProcessEventType.values().length );
-
-        byte[] stream = ":maven-surefire-event:\u000E:xxx".getBytes( UTF_8 );
-        Channel channel = new Channel( stream, 1 );
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-
-        Memento memento = thread.new Memento();
-        memento.bb.position( 0 ).limit( 0 );
-        memento.setCharset( UTF_8 );
-        thread.readEventType( eventTypes, memento );
-    }
-
-    @Test( expected = EventConsumerThread.MalformedFrameException.class )
-    public void shouldEventTypeReachedMalformedHeader() throws Exception
-    {
-        Map<Segment, ForkedProcessEventType> eventTypes = mapEventTypes();
-        assertThat( eventTypes )
-            .hasSize( ForkedProcessEventType.values().length );
-
-        byte[] stream = ":xxxxx-xxxxxxxx-xxxxx:\u000E:xxx".getBytes( UTF_8 );
-        Channel channel = new Channel( stream, 1 );
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-
-        Memento memento = thread.new Memento();
-        memento.bb.position( 0 ).limit( 0 );
-        memento.setCharset( UTF_8 );
-        thread.readEventType( eventTypes, memento );
-    }
-
-    @Test
-    public void shouldMapSegmentToEventType()
-    {
-        Map<Segment, RunMode> map = mapRunModes();
-
-        assertThat( map )
-            .hasSize( 2 );
-
-        byte[] stream = "normal-run".getBytes( US_ASCII );
-        Segment segment = new Segment( stream, 0, stream.length );
-        assertThat( map.get( segment ) )
-            .isEqualTo( NORMAL_RUN );
-
-        stream = "rerun-test-after-failure".getBytes( US_ASCII );
-        segment = new Segment( stream, 0, stream.length );
-        assertThat( map.get( segment ) )
-            .isEqualTo( RERUN_TEST_AFTER_FAILURE );
-    }
-
-    @Test
-    public void shouldReadEmptyString() throws Exception
-    {
-        byte[] stream = "\u0000\u0000\u0000\u0000::".getBytes( UTF_8 );
-        Channel channel = new Channel( stream, 1 );
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-
-        Memento memento = thread.new Memento();
-        memento.bb.position( 0 ).limit( 0 );
-        memento.setCharset( UTF_8 );
-
-        assertThat( thread.readString( memento ) )
-            .isEmpty();
-    }
-
-    @Test
-    public void shouldReadNullString() throws Exception
-    {
-        byte[] stream = "\u0000\u0000\u0000\u0001:\u0000:".getBytes( UTF_8 );
-        Channel channel = new Channel( stream, 1 );
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-
-        Memento memento = thread.new Memento();
-        memento.bb.position( 0 ).limit( 0 );
-        memento.setCharset( UTF_8 );
-
-        assertThat( thread.readString( memento ) )
-            .isNull();
-    }
-
-    @Test
-    public void shouldReadSingleCharString() throws Exception
-    {
-        byte[] stream = "\u0000\u0000\u0000\u0001:A:".getBytes( UTF_8 );
-        Channel channel = new Channel( stream, 1 );
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-
-        Memento memento = thread.new Memento();
-        memento.bb.position( 0 ).limit( 0 );
-        memento.setCharset( UTF_8 );
-
-        assertThat( thread.readString( memento ) )
-            .isEqualTo( "A" );
-    }
-
-    @Test
-    public void shouldReadThreeCharactersString() throws Exception
-    {
-        byte[] stream = "\u0000\u0000\u0000\u0003:ABC:".getBytes( UTF_8 );
-        Channel channel = new Channel( stream, 1 );
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-
-        Memento memento = thread.new Memento();
-        memento.bb.position( 0 ).limit( 0 );
-        memento.setCharset( UTF_8 );
-
-        assertThat( thread.readString( memento ) )
-            .isEqualTo( "ABC" );
-    }
-
-    @Test
-    public void shouldReadDefaultCharset() throws Exception
-    {
-        byte[] stream = "\u0005:UTF-8:".getBytes( US_ASCII );
-        Channel channel = new Channel( stream, 1 );
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-
-        Memento memento = thread.new Memento();
-        memento.bb.position( 0 ).limit( 0 );
-        memento.setCharset( UTF_8 );
-
-        assertThat( thread.readCharset( memento ) )
-            .isNotNull()
-            .isEqualTo( UTF_8 );
-    }
-
-    @Test
-    public void shouldReadNonDefaultCharset() throws Exception
-    {
-        byte[] stream = ( (char) 10 + ":ISO_8859_1:" ).getBytes( US_ASCII );
-        Channel channel = new Channel( stream, 1 );
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-
-        Memento memento = thread.new Memento();
-        memento.bb.position( 0 ).limit( 0 );
-        memento.setCharset( UTF_8 );
-
-        assertThat( thread.readCharset( memento ) )
-            .isNotNull()
-            .isEqualTo( ISO_8859_1 );
-    }
-
-    @Test
-    public void shouldSetNonDefaultCharset()
-    {
-        byte[] stream = {};
-        Channel channel = new Channel( stream, 1 );
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-        Memento memento = thread.new Memento();
-
-        memento.setCharset( ISO_8859_1 );
-        assertThat( memento.getDecoder().charset() ).isEqualTo( ISO_8859_1 );
-
-        memento.setCharset( UTF_8 );
-        assertThat( memento.getDecoder().charset() ).isEqualTo( UTF_8 );
-
-        memento.reset();
-        assertThat( memento.getDecoder() ).isNotNull();
-        assertThat( memento.getDecoder().charset() ).isEqualTo( UTF_8 );
-    }
-
-    @Test( expected = EventConsumerThread.MalformedFrameException.class )
-    public void malformedCharset() throws Exception
-    {
-        byte[] stream = ( (char) 8 + ":ISO_8859:" ).getBytes( US_ASCII );
-        Channel channel = new Channel( stream, 1 );
-        EventConsumerThread thread = new EventConsumerThread( "t", channel,
-            new MockEventHandler<Event>(), COUNTDOWN_CLOSEABLE, new MockForkNodeArguments() );
-
-        Memento memento = thread.new Memento();
-        memento.bb.position( 0 ).limit( 0 );
-        memento.setCharset( UTF_8 );
-
-        thread.readCharset( memento );
-    }
-
-    @Test
-    public void shouldMapEventTypeToSegmentType()
-    {
-        SegmentType[] segmentTypes = nextSegmentType( BOOTERCODE_BYE );
-        assertThat( segmentTypes )
-            .hasSize( 1 )
-            .containsOnly( END_OF_FRAME );
-
-        segmentTypes = nextSegmentType( BOOTERCODE_STOP_ON_NEXT_TEST );
-        assertThat( segmentTypes )
-            .hasSize( 1 )
-            .containsOnly( END_OF_FRAME );
-
-        segmentTypes = nextSegmentType( BOOTERCODE_NEXT_TEST );
-        assertThat( segmentTypes )
-            .hasSize( 1 )
-            .containsOnly( END_OF_FRAME );
-
-        segmentTypes = nextSegmentType( BOOTERCODE_CONSOLE_ERROR );
-        assertThat( segmentTypes )
-            .hasSize( 5 )
-            .satisfies( new InOrder( STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING, END_OF_FRAME ) );
-
-        segmentTypes = nextSegmentType( ForkedProcessEventType.BOOTERCODE_JVM_EXIT_ERROR );
-        assertThat( segmentTypes )
-            .hasSize( 5 )
-            .satisfies( new InOrder( STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING, END_OF_FRAME ) );
-
-        segmentTypes = nextSegmentType( BOOTERCODE_CONSOLE_INFO );
-        assertThat( segmentTypes )
-            .hasSize( 3 )
-            .satisfies( new InOrder( STRING_ENCODING, DATA_STRING, END_OF_FRAME ) );
-
-        segmentTypes = nextSegmentType( ForkedProcessEventType.BOOTERCODE_CONSOLE_DEBUG );
-        assertThat( segmentTypes )
-            .hasSize( 3 )
-            .satisfies( new InOrder( STRING_ENCODING, DATA_STRING, END_OF_FRAME ) );
-
-        segmentTypes = nextSegmentType( ForkedProcessEventType.BOOTERCODE_CONSOLE_WARNING );
-        assertThat( segmentTypes )
-            .hasSize( 3 )
-            .satisfies( new InOrder( STRING_ENCODING, DATA_STRING, END_OF_FRAME ) );
-
-        segmentTypes = nextSegmentType( BOOTERCODE_STDOUT );
-        assertThat( segmentTypes )
-            .hasSize( 4 )
-            .satisfies( new InOrder( RUN_MODE, STRING_ENCODING, DATA_STRING, END_OF_FRAME ) );
-
-        segmentTypes = nextSegmentType( ForkedProcessEventType.BOOTERCODE_STDOUT_NEW_LINE );
-        assertThat( segmentTypes )
-            .hasSize( 4 )
-            .satisfies( new InOrder( RUN_MODE, STRING_ENCODING, DATA_STRING, END_OF_FRAME ) );
-
-        segmentTypes = nextSegmentType( ForkedProcessEventType.BOOTERCODE_STDERR );
-        assertThat( segmentTypes )
-            .hasSize( 4 )
-            .satisfies( new InOrder( RUN_MODE, STRING_ENCODING, DATA_STRING, END_OF_FRAME ) );
-
-        segmentTypes = nextSegmentType( ForkedProcessEventType.BOOTERCODE_STDERR_NEW_LINE );
-        assertThat( segmentTypes )
-            .hasSize( 4 )
-            .satisfies( new InOrder( RUN_MODE, STRING_ENCODING, DATA_STRING, END_OF_FRAME ) );
-
-        segmentTypes = nextSegmentType( BOOTERCODE_SYSPROPS );
-        assertThat( segmentTypes )
-            .hasSize( 5 )
-            .satisfies( new InOrder( RUN_MODE, STRING_ENCODING, DATA_STRING, DATA_STRING, END_OF_FRAME ) );
-
-        segmentTypes = nextSegmentType( BOOTERCODE_TESTSET_STARTING );
-        assertThat( segmentTypes )
-            .hasSize( 13 )
-            .satisfies( new InOrder( RUN_MODE, STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING, DATA_STRING,
-                DATA_STRING, DATA_STRING, DATA_INT, DATA_STRING, DATA_STRING, DATA_STRING, END_OF_FRAME ) );
-
-        segmentTypes = nextSegmentType( ForkedProcessEventType.BOOTERCODE_TESTSET_COMPLETED );
-        assertThat( segmentTypes )
-            .hasSize( 13 )
-            .satisfies( new InOrder( RUN_MODE, STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING, DATA_STRING,
-                DATA_STRING, DATA_STRING, DATA_INT, DATA_STRING, DATA_STRING, DATA_STRING, END_OF_FRAME ) );
-
-        segmentTypes = nextSegmentType( ForkedProcessEventType.BOOTERCODE_TEST_STARTING );
-        assertThat( segmentTypes )
-            .hasSize( 13 )
-            .satisfies( new InOrder( RUN_MODE, STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING, DATA_STRING,
-                DATA_STRING, DATA_STRING, DATA_INT, DATA_STRING, DATA_STRING, DATA_STRING, END_OF_FRAME ) );
-
-        segmentTypes = nextSegmentType( BOOTERCODE_TEST_SUCCEEDED );
-        assertThat( segmentTypes )
-            .hasSize( 13 )
-            .satisfies( new InOrder( RUN_MODE, STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING, DATA_STRING,
-                DATA_STRING, DATA_STRING, DATA_INT, DATA_STRING, DATA_STRING, DATA_STRING, END_OF_FRAME ) );
-
-        segmentTypes = nextSegmentType( BOOTERCODE_TEST_FAILED );
-        assertThat( segmentTypes )
-            .hasSize( 13 )
-            .satisfies( new InOrder( RUN_MODE, STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING, DATA_STRING,
-                DATA_STRING, DATA_STRING, DATA_INT, DATA_STRING, DATA_STRING, DATA_STRING, END_OF_FRAME ) );
-
-        segmentTypes = nextSegmentType( ForkedProcessEventType.BOOTERCODE_TEST_SKIPPED );
-        assertThat( segmentTypes )
-            .hasSize( 13 )
-            .satisfies( new InOrder( RUN_MODE, STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING, DATA_STRING,
-                DATA_STRING, DATA_STRING, DATA_INT, DATA_STRING, DATA_STRING, DATA_STRING, END_OF_FRAME ) );
-
-        segmentTypes = nextSegmentType( ForkedProcessEventType.BOOTERCODE_TEST_ERROR );
-        assertThat( segmentTypes )
-            .hasSize( 13 )
-            .satisfies( new InOrder( RUN_MODE, STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING, DATA_STRING,
-                DATA_STRING, DATA_STRING, DATA_INT, DATA_STRING, DATA_STRING, DATA_STRING, END_OF_FRAME ) );
-
-        segmentTypes = nextSegmentType( ForkedProcessEventType.BOOTERCODE_TEST_ASSUMPTIONFAILURE );
-        assertThat( segmentTypes )
-            .hasSize( 13 )
-            .satisfies( new InOrder( RUN_MODE, STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING, DATA_STRING,
-                DATA_STRING, DATA_STRING, DATA_INT, DATA_STRING, DATA_STRING, DATA_STRING, END_OF_FRAME ) );
-    }
-
-    @Test
-    public void shouldCreateEvent()
-    {
-        Event event = toEvent( BOOTERCODE_BYE, NORMAL_RUN, emptyList() );
-        assertThat( event )
-            .isInstanceOf( ControlByeEvent.class );
-
-        event = toEvent( BOOTERCODE_STOP_ON_NEXT_TEST, NORMAL_RUN, emptyList() );
-        assertThat( event )
-            .isInstanceOf( ControlStopOnNextTestEvent.class );
-
-        event = toEvent( BOOTERCODE_NEXT_TEST, NORMAL_RUN, emptyList() );
-        assertThat( event )
-            .isInstanceOf( ControlNextTestEvent.class );
-
-        List data = asList( "1", "2", "3" );
-        event = toEvent( BOOTERCODE_CONSOLE_ERROR, NORMAL_RUN, data );
-        assertThat( event )
-            .isInstanceOf( ConsoleErrorEvent.class );
-        ConsoleErrorEvent consoleErrorEvent = (ConsoleErrorEvent) event;
-        assertThat( consoleErrorEvent.getStackTraceWriter().getThrowable().getLocalizedMessage() )
-            .isEqualTo( "1" );
-        assertThat( consoleErrorEvent.getStackTraceWriter().smartTrimmedStackTrace() )
-            .isEqualTo( "2" );
-        assertThat( consoleErrorEvent.getStackTraceWriter().writeTraceToString() )
-            .isEqualTo( "3" );
-
-        data = asList( null, null, null );
-        event = toEvent( BOOTERCODE_CONSOLE_ERROR, NORMAL_RUN, data );
-        assertThat( event )
-            .isInstanceOf( ConsoleErrorEvent.class );
-        consoleErrorEvent = (ConsoleErrorEvent) event;
-        assertThat( consoleErrorEvent.getStackTraceWriter() )
-            .isNull();
-
-        data = asList( "1", "2", "3" );
-        event = toEvent( BOOTERCODE_JVM_EXIT_ERROR, NORMAL_RUN, data );
-        assertThat( event )
-            .isInstanceOf( JvmExitErrorEvent.class );
-        JvmExitErrorEvent jvmExitErrorEvent = (JvmExitErrorEvent) event;
-        assertThat( jvmExitErrorEvent.getStackTraceWriter().getThrowable().getLocalizedMessage() )
-            .isEqualTo( "1" );
-        assertThat( jvmExitErrorEvent.getStackTraceWriter().smartTrimmedStackTrace() )
-            .isEqualTo( "2" );
-        assertThat( jvmExitErrorEvent.getStackTraceWriter().writeTraceToString() )
-            .isEqualTo( "3" );
-
-        data = asList( null, null, null );
-        event = toEvent( BOOTERCODE_JVM_EXIT_ERROR, NORMAL_RUN, data );
-        assertThat( event )
-            .isInstanceOf( JvmExitErrorEvent.class );
-        jvmExitErrorEvent = (JvmExitErrorEvent) event;
-        assertThat( jvmExitErrorEvent.getStackTraceWriter() )
-            .isNull();
-
-        data = singletonList( "m" );
-        event = toEvent( BOOTERCODE_CONSOLE_INFO, NORMAL_RUN, data );
-        assertThat( event ).isInstanceOf( ConsoleInfoEvent.class );
-        assertThat( ( (ConsoleInfoEvent) event ).getMessage() ).isEqualTo( "m" );
-
-        data = singletonList( "" );
-        event = toEvent( BOOTERCODE_CONSOLE_WARNING, NORMAL_RUN, data );
-        assertThat( event ).isInstanceOf( ConsoleWarningEvent.class );
-        assertThat( ( (ConsoleWarningEvent) event ).getMessage() ).isEmpty();
-
-        data = singletonList( null );
-        event = toEvent( BOOTERCODE_CONSOLE_DEBUG, NORMAL_RUN, data );
-        assertThat( event ).isInstanceOf( ConsoleDebugEvent.class );
-        assertThat( ( (ConsoleDebugEvent) event ).getMessage() ).isNull();
-
-        data = singletonList( "m" );
-        event = toEvent( BOOTERCODE_STDOUT, NORMAL_RUN, data );
-        assertThat( event ).isInstanceOf( StandardStreamOutEvent.class );
-        assertThat( ( (StandardStreamOutEvent) event ).getMessage() ).isEqualTo( "m" );
-        assertThat( ( (StandardStreamOutEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
-
-        data = singletonList( null );
-        event = toEvent( BOOTERCODE_STDOUT_NEW_LINE, RERUN_TEST_AFTER_FAILURE, data );
-        assertThat( event ).isInstanceOf( StandardStreamOutWithNewLineEvent.class );
-        assertThat( ( (StandardStreamOutWithNewLineEvent) event ).getMessage() ).isNull();
-        assertThat( ( (StandardStreamOutWithNewLineEvent) event ).getRunMode() ).isEqualTo( RERUN_TEST_AFTER_FAILURE );
-
-        data = singletonList( null );
-        event = toEvent( BOOTERCODE_STDERR, RERUN_TEST_AFTER_FAILURE, data );
-        assertThat( event ).isInstanceOf( StandardStreamErrEvent.class );
-        assertThat( ( (StandardStreamErrEvent) event ).getMessage() ).isNull();
-        assertThat( ( (StandardStreamErrEvent) event ).getRunMode() ).isEqualTo( RERUN_TEST_AFTER_FAILURE );
-
-        data = singletonList( "abc" );
-        event = toEvent( BOOTERCODE_STDERR_NEW_LINE, NORMAL_RUN, data );
-        assertThat( event ).isInstanceOf( StandardStreamErrWithNewLineEvent.class );
-        assertThat( ( (StandardStreamErrWithNewLineEvent) event ).getMessage() ).isEqualTo( "abc" );
-        assertThat( ( (StandardStreamErrWithNewLineEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
-
-        data = asList( "key", "value" );
-        event = toEvent( BOOTERCODE_SYSPROPS, NORMAL_RUN, data );
-        assertThat( event ).isInstanceOf( SystemPropertyEvent.class );
-        assertThat( ( (SystemPropertyEvent) event ).getKey() ).isEqualTo( "key" );
-        assertThat( ( (SystemPropertyEvent) event ).getValue() ).isEqualTo( "value" );
-        assertThat( ( (SystemPropertyEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
-
-        data = asList( "source", "sourceText", "name", "nameText", "group", "message", 5,
-            "traceMessage", "smartTrimmedStackTrace", "stackTrace" );
-        event = toEvent( BOOTERCODE_TESTSET_STARTING, NORMAL_RUN, data );
-        assertThat( event ).isInstanceOf( TestsetStartingEvent.class );
-        assertThat( ( (TestsetStartingEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
-        assertThat( ( (TestsetStartingEvent) event ).getReportEntry().getSourceName() ).isEqualTo( "source" );
-        assertThat( ( (TestsetStartingEvent) event ).getReportEntry().getSourceText() ).isEqualTo( "sourceText" );
-        assertThat( ( (TestsetStartingEvent) event ).getReportEntry().getName() ).isEqualTo( "name" );
-        assertThat( ( (TestsetStartingEvent) event ).getReportEntry().getNameText() ).isEqualTo( "nameText" );
-        assertThat( ( (TestsetStartingEvent) event ).getReportEntry().getGroup() ).isEqualTo( "group" );
-        assertThat( ( (TestsetStartingEvent) event ).getReportEntry().getMessage() ).isEqualTo( "message" );
-        assertThat( ( (TestsetStartingEvent) event ).getReportEntry().getElapsed() ).isEqualTo( 5 );
-        assertThat( ( (TestsetStartingEvent) event )
-            .getReportEntry().getStackTraceWriter().getThrowable().getLocalizedMessage() )
-            .isEqualTo( "traceMessage" );
-        assertThat( ( (TestsetStartingEvent) event ).getReportEntry().getStackTraceWriter().smartTrimmedStackTrace() )
-            .isEqualTo( "smartTrimmedStackTrace" );
-        assertThat( ( (TestsetStartingEvent) event ).getReportEntry().getStackTraceWriter().writeTraceToString() )
-            .isEqualTo( "stackTrace" );
-
-        data = asList( "source", "sourceText", "name", "nameText", "group", null, 5,
-            "traceMessage", "smartTrimmedStackTrace", "stackTrace" );
-        event = toEvent( BOOTERCODE_TESTSET_COMPLETED, NORMAL_RUN, data );
-        assertThat( event ).isInstanceOf( TestsetCompletedEvent.class );
-        assertThat( ( (TestsetCompletedEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
-        assertThat( ( (TestsetCompletedEvent) event ).getReportEntry().getSourceName() ).isEqualTo( "source" );
-        assertThat( ( (TestsetCompletedEvent) event ).getReportEntry().getSourceText() ).isEqualTo( "sourceText" );
-        assertThat( ( (TestsetCompletedEvent) event ).getReportEntry().getName() ).isEqualTo( "name" );
-        assertThat( ( (TestsetCompletedEvent) event ).getReportEntry().getNameText() ).isEqualTo( "nameText" );
-        assertThat( ( (TestsetCompletedEvent) event ).getReportEntry().getGroup() ).isEqualTo( "group" );
-        assertThat( ( (TestsetCompletedEvent) event ).getReportEntry().getMessage() ).isNull();
-        assertThat( ( (TestsetCompletedEvent) event ).getReportEntry().getElapsed() ).isEqualTo( 5 );
-        assertThat( ( (TestsetCompletedEvent) event )
-            .getReportEntry().getStackTraceWriter().getThrowable().getLocalizedMessage() )
-            .isEqualTo( "traceMessage" );
-        assertThat( ( (TestsetCompletedEvent) event ).getReportEntry().getStackTraceWriter().smartTrimmedStackTrace() )
-            .isEqualTo( "smartTrimmedStackTrace" );
-        assertThat( ( (TestsetCompletedEvent) event ).getReportEntry().getStackTraceWriter().writeTraceToString() )
-            .isEqualTo( "stackTrace" );
-
-        data = asList( "source", "sourceText", "name", "nameText", "group", "message", 5,
-            null, "smartTrimmedStackTrace", "stackTrace" );
-        event = toEvent( BOOTERCODE_TEST_STARTING, NORMAL_RUN, data );
-        assertThat( event ).isInstanceOf( TestStartingEvent.class );
-        assertThat( ( (TestStartingEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
-        assertThat( ( (TestStartingEvent) event ).getReportEntry().getSourceName() ).isEqualTo( "source" );
-        assertThat( ( (TestStartingEvent) event ).getReportEntry().getSourceText() ).isEqualTo( "sourceText" );
-        assertThat( ( (TestStartingEvent) event ).getReportEntry().getName() ).isEqualTo( "name" );
-        assertThat( ( (TestStartingEvent) event ).getReportEntry().getNameText() ).isEqualTo( "nameText" );
-        assertThat( ( (TestStartingEvent) event ).getReportEntry().getGroup() ).isEqualTo( "group" );
-        assertThat( ( (TestStartingEvent) event ).getReportEntry().getMessage() ).isEqualTo( "message" );
-        assertThat( ( (TestStartingEvent) event ).getReportEntry().getElapsed() ).isEqualTo( 5 );
-        assertThat( ( (TestStartingEvent) event )
-            .getReportEntry().getStackTraceWriter().getThrowable().getLocalizedMessage() )
-            .isNull();
-        assertThat( ( (TestStartingEvent) event ).getReportEntry().getStackTraceWriter().smartTrimmedStackTrace() )
-            .isEqualTo( "smartTrimmedStackTrace" );
-        assertThat( ( (TestStartingEvent) event ).getReportEntry().getStackTraceWriter().writeTraceToString() )
-            .isEqualTo( "stackTrace" );
-
-        data = asList( "source", "sourceText", "name", "nameText", "group", "message", 5, null, null, null );
-        event = toEvent( BOOTERCODE_TEST_SUCCEEDED, NORMAL_RUN, data );
-        assertThat( event ).isInstanceOf( TestSucceededEvent.class );
-        assertThat( ( (TestSucceededEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
-        assertThat( ( (TestSucceededEvent) event ).getReportEntry().getSourceName() ).isEqualTo( "source" );
-        assertThat( ( (TestSucceededEvent) event ).getReportEntry().getSourceText() ).isEqualTo( "sourceText" );
-        assertThat( ( (TestSucceededEvent) event ).getReportEntry().getName() ).isEqualTo( "name" );
-        assertThat( ( (TestSucceededEvent) event ).getReportEntry().getNameText() ).isEqualTo( "nameText" );
-        assertThat( ( (TestSucceededEvent) event ).getReportEntry().getGroup() ).isEqualTo( "group" );
-        assertThat( ( (TestSucceededEvent) event ).getReportEntry().getMessage() ).isEqualTo( "message" );
-        assertThat( ( (TestSucceededEvent) event ).getReportEntry().getElapsed() ).isEqualTo( 5 );
-        assertThat( ( (TestSucceededEvent) event ).getReportEntry().getStackTraceWriter() ).isNull();
-
-        data = asList( "source", null, "name", null, "group", null, 5,
-            "traceMessage", "smartTrimmedStackTrace", "stackTrace" );
-        event = toEvent( BOOTERCODE_TEST_FAILED, RERUN_TEST_AFTER_FAILURE, data );
-        assertThat( event ).isInstanceOf( TestFailedEvent.class );
-        assertThat( ( (TestFailedEvent) event ).getRunMode() ).isEqualTo( RERUN_TEST_AFTER_FAILURE );
-        assertThat( ( (TestFailedEvent) event ).getReportEntry().getSourceName() ).isEqualTo( "source" );
-        assertThat( ( (TestFailedEvent) event ).getReportEntry().getSourceText() ).isNull();
-        assertThat( ( (TestFailedEvent) event ).getReportEntry().getName() ).isEqualTo( "name" );
-        assertThat( ( (TestFailedEvent) event ).getReportEntry().getNameText() ).isNull();
-        assertThat( ( (TestFailedEvent) event ).getReportEntry().getGroup() ).isEqualTo( "group" );
-        assertThat( ( (TestFailedEvent) event ).getReportEntry().getMessage() ).isNull();
-        assertThat( ( (TestFailedEvent) event ).getReportEntry().getElapsed() ).isEqualTo( 5 );
-        assertThat( ( (TestFailedEvent) event )
-            .getReportEntry().getStackTraceWriter().getThrowable().getLocalizedMessage() )
-            .isEqualTo( "traceMessage" );
-        assertThat( ( (TestFailedEvent) event ).getReportEntry().getStackTraceWriter().smartTrimmedStackTrace() )
-            .isEqualTo( "smartTrimmedStackTrace" );
-        assertThat( ( (TestFailedEvent) event ).getReportEntry().getStackTraceWriter().writeTraceToString() )
-            .isEqualTo( "stackTrace" );
-
-        data = asList( "source", null, "name", null, null, null, 5, null, null, "stackTrace" );
-        event = toEvent( BOOTERCODE_TEST_SKIPPED, NORMAL_RUN, data );
-        assertThat( event ).isInstanceOf( TestSkippedEvent.class );
-        assertThat( ( (TestSkippedEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
-        assertThat( ( (TestSkippedEvent) event ).getReportEntry().getSourceName() ).isEqualTo( "source" );
-        assertThat( ( (TestSkippedEvent) event ).getReportEntry().getSourceText() ).isNull();
-        assertThat( ( (TestSkippedEvent) event ).getReportEntry().getName() ).isEqualTo( "name" );
-        assertThat( ( (TestSkippedEvent) event ).getReportEntry().getNameText() ).isNull();
-        assertThat( ( (TestSkippedEvent) event ).getReportEntry().getGroup() ).isNull();
-        assertThat( ( (TestSkippedEvent) event ).getReportEntry().getMessage() ).isNull();
-        assertThat( ( (TestSkippedEvent) event ).getReportEntry().getElapsed() ).isEqualTo( 5 );
-        assertThat( ( (TestSkippedEvent) event )
-            .getReportEntry().getStackTraceWriter().getThrowable().getLocalizedMessage() )
-            .isNull();
-        assertThat( ( (TestSkippedEvent) event )
-            .getReportEntry().getStackTraceWriter().smartTrimmedStackTrace() )
-            .isNull();
-        assertThat( ( (TestSkippedEvent) event )
-            .getReportEntry().getStackTraceWriter().writeTraceToString() )
-            .isEqualTo( "stackTrace" );
-
-        data = asList( "source", null, "name", "nameText", null, null, 0, null, null, "stackTrace" );
-        event = toEvent( BOOTERCODE_TEST_ERROR, NORMAL_RUN, data );
-        assertThat( event ).isInstanceOf( TestErrorEvent.class );
-        assertThat( ( (TestErrorEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
-        assertThat( ( (TestErrorEvent) event ).getReportEntry().getSourceName() ).isEqualTo( "source" );
-        assertThat( ( (TestErrorEvent) event ).getReportEntry().getSourceText() ).isNull();
-        assertThat( ( (TestErrorEvent) event ).getReportEntry().getName() ).isEqualTo( "name" );
-        assertThat( ( (TestErrorEvent) event ).getReportEntry().getNameText() ).isEqualTo( "nameText" );
-        assertThat( ( (TestErrorEvent) event ).getReportEntry().getGroup() ).isNull();
-        assertThat( ( (TestErrorEvent) event ).getReportEntry().getMessage() ).isNull();
-        assertThat( ( (TestErrorEvent) event ).getReportEntry().getElapsed() ).isZero();
-        assertThat( ( (TestErrorEvent) event )
-            .getReportEntry().getStackTraceWriter().getThrowable().getLocalizedMessage() )
-            .isNull();
-        assertThat( ( (TestErrorEvent) event )
-            .getReportEntry().getStackTraceWriter().smartTrimmedStackTrace() )
-            .isNull();
-        assertThat( ( (TestErrorEvent) event )
-            .getReportEntry().getStackTraceWriter().writeTraceToString() )
-            .isEqualTo( "stackTrace" );
-
-        data = asList( "source", null, "name", null, "group", null, 5, null, null, "stackTrace" );
-        event = toEvent( BOOTERCODE_TEST_ASSUMPTIONFAILURE, NORMAL_RUN, data );
-        assertThat( event ).isInstanceOf( TestAssumptionFailureEvent.class );
-        assertThat( ( (TestAssumptionFailureEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
-        assertThat( ( (TestAssumptionFailureEvent) event ).getReportEntry().getSourceName() ).isEqualTo( "source" );
-        assertThat( ( (TestAssumptionFailureEvent) event ).getReportEntry().getSourceText() ).isNull();
-        assertThat( ( (TestAssumptionFailureEvent) event ).getReportEntry().getName() ).isEqualTo( "name" );
-        assertThat( ( (TestAssumptionFailureEvent) event ).getReportEntry().getNameText() ).isNull();
-        assertThat( ( (TestAssumptionFailureEvent) event ).getReportEntry().getGroup() ).isEqualTo( "group" );
-        assertThat( ( (TestAssumptionFailureEvent) event ).getReportEntry().getMessage() ).isNull();
-        assertThat( ( (TestAssumptionFailureEvent) event ).getReportEntry().getElapsed() ).isEqualTo( 5 );
-        assertThat( ( (TestAssumptionFailureEvent) event )
-            .getReportEntry().getStackTraceWriter().getThrowable().getLocalizedMessage() )
-            .isNull();
-        assertThat( ( (TestAssumptionFailureEvent) event )
-            .getReportEntry().getStackTraceWriter().smartTrimmedStackTrace() )
-            .isNull();
-        assertThat( ( (TestAssumptionFailureEvent) event )
-            .getReportEntry().getStackTraceWriter().writeTraceToString() )
-            .isEqualTo( "stackTrace" );
+            .isLessThanOrEqualTo( 3_650L );
     }
 
     private static class Channel implements ReadableByteChannel
@@ -1117,13 +145,13 @@ public class EventConsumerThreadTest
         @Override
         public int read( ByteBuffer dst )
         {
-            if ( i == bytes.length )
+            if ( remaining() == 0 )
             {
                 return -1;
             }
             else if ( dst.hasRemaining() )
             {
-                int length = min( min( chunkSize, bytes.length - i ), dst.remaining() ) ;
+                int length = min( min( chunkSize, remaining() ), dst.remaining() ) ;
                 dst.put( bytes, i, length );
                 i += length;
                 return length;
@@ -1134,6 +162,11 @@ public class EventConsumerThreadTest
             }
         }
 
+        protected final int remaining()
+        {
+            return bytes.length - i;
+        }
+
         @Override
         public boolean isOpen()
         {
@@ -1154,14 +187,6 @@ public class EventConsumerThreadTest
         }
     }
 
-    private static class MockEventHandler<T> implements EventHandler<T>
-    {
-        @Override
-        public void handleEvent( @Nonnull T event )
-        {
-        }
-    }
-
     private static class MockForkNodeArguments implements ForkNodeArguments
     {
         @Nonnull
@@ -1202,36 +227,17 @@ public class EventConsumerThreadTest
         {
             return null;
         }
-    }
-
-    private static class InOrder extends Condition<Object[]>
-    {
-        private final SegmentType[] expected;
 
-        InOrder( SegmentType... expected )
+        @Override
+        public File getEventStreamBinaryFile()
         {
-            this.expected = expected;
+            return null;
         }
 
         @Override
-        public boolean matches( Object[] values )
+        public File getCommandStreamBinaryFile()
         {
-            if ( values == null && expected == null )
-            {
-                return true;
-            }
-            else if ( values != null && expected != null && values.length == expected.length )
-            {
-                boolean matches = true;
-                for ( int i = 0; i < values.length; i++ )
-                {
-
-                    assertThat( values[i] ).isInstanceOf( SegmentType.class );
-                    matches &= values[i] == expected[i];
-                }
-                return matches;
-            }
-            return false;
+            return null;
         }
     }
 }
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/ForkedProcessEventNotifierTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/ForkedProcessEventNotifierTest.java
index 3dbde1f..558c021 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/ForkedProcessEventNotifierTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/ForkedProcessEventNotifierTest.java
@@ -31,15 +31,15 @@ import org.apache.maven.plugin.surefire.booterclient.output.ForkedProcessStringE
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.plugin.surefire.log.api.ConsoleLoggerUtils;
 import org.apache.maven.surefire.api.event.Event;
+import org.apache.maven.surefire.api.fork.ForkNodeArguments;
 import org.apache.maven.surefire.api.report.ReportEntry;
 import org.apache.maven.surefire.api.report.RunMode;
 import org.apache.maven.surefire.api.report.SafeThrowable;
 import org.apache.maven.surefire.api.report.StackTraceWriter;
 import org.apache.maven.surefire.api.util.internal.ObjectUtils;
 import org.apache.maven.surefire.api.util.internal.WritableBufferedByteChannel;
-import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder;
+import org.apache.maven.surefire.booter.spi.EventChannelEncoder;
 import org.apache.maven.surefire.extensions.EventHandler;
-import org.apache.maven.surefire.extensions.ForkNodeArguments;
 import org.apache.maven.surefire.extensions.util.CountdownCloseable;
 import org.junit.Rule;
 import org.junit.Test;
@@ -106,9 +106,9 @@ public class ForkedProcessEventNotifierTest
         {
             final Stream out = Stream.newStream();
             WritableBufferedByteChannel wChannel = newBufferedChannel( out );
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( wChannel );
+            EventChannelEncoder encoder = new EventChannelEncoder( wChannel );
             Map<String, String> props = ObjectUtils.systemProps();
-            encoder.sendSystemProperties( props );
+            encoder.systemProperties( props );
             wChannel.close();
 
             ForkedProcessEventNotifier notifier = new ForkedProcessEventNotifier();
@@ -138,154 +138,10 @@ public class ForkedProcessEventNotifierTest
         }
 
         @Test
-        public void shouldRecognizeEmptyStream4ReportEntry()
-        {
-            ReportEntry reportEntry = EventConsumerThread.newReportEntry( "", "", "", "", "", "", null, "", "", "" );
-            assertThat( reportEntry ).isNotNull();
-            assertThat( reportEntry.getStackTraceWriter() ).isNotNull();
-            assertThat( reportEntry.getStackTraceWriter().smartTrimmedStackTrace() ).isEmpty();
-            assertThat( reportEntry.getStackTraceWriter().writeTraceToString() ).isEmpty();
-            assertThat( reportEntry.getStackTraceWriter().writeTrimmedTraceToString() ).isEmpty();
-            assertThat( reportEntry.getSourceName() ).isEmpty();
-            assertThat( reportEntry.getSourceText() ).isEmpty();
-            assertThat( reportEntry.getName() ).isEmpty();
-            assertThat( reportEntry.getNameText() ).isEmpty();
-            assertThat( reportEntry.getGroup() ).isEmpty();
-            assertThat( reportEntry.getNameWithGroup() ).isEmpty();
-            assertThat( reportEntry.getMessage() ).isEmpty();
-            assertThat( reportEntry.getElapsed() ).isNull();
-        }
-
-        @Test
-        @SuppressWarnings( "checkstyle:magicnumber" )
-        public void testCreatingReportEntry()
-        {
-            final String exceptionMessage = "msg";
-            final String smartStackTrace = "MyTest:86 >> Error";
-            final String stackTrace = "Exception: msg\ntrace line 1\ntrace line 2";
-            final String trimmedStackTrace = "trace line 1\ntrace line 2";
-
-            SafeThrowable safeThrowable = new SafeThrowable( exceptionMessage );
-            StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
-            when( stackTraceWriter.getThrowable() ).thenReturn( safeThrowable );
-            when( stackTraceWriter.smartTrimmedStackTrace() ).thenReturn( smartStackTrace );
-            when( stackTraceWriter.writeTrimmedTraceToString() ).thenReturn( trimmedStackTrace );
-            when( stackTraceWriter.writeTraceToString() ).thenReturn( stackTrace );
-
-            ReportEntry reportEntry = mock( ReportEntry.class );
-            when( reportEntry.getElapsed() ).thenReturn( 102 );
-            when( reportEntry.getGroup() ).thenReturn( "this group" );
-            when( reportEntry.getMessage() ).thenReturn( "skipped test" );
-            when( reportEntry.getName() ).thenReturn( "my test" );
-            when( reportEntry.getNameText() ).thenReturn( "my display name" );
-            when( reportEntry.getNameWithGroup() ).thenReturn( "name with group" );
-            when( reportEntry.getSourceName() ).thenReturn( "pkg.MyTest" );
-            when( reportEntry.getSourceText() ).thenReturn( "test class display name" );
-            when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
-
-            ReportEntry decodedReportEntry = EventConsumerThread.newReportEntry( reportEntry.getSourceName(),
-                reportEntry.getSourceText(), reportEntry.getName(), reportEntry.getNameText(), reportEntry.getGroup(),
-                reportEntry.getMessage(), null, null, null, null );
-
-            assertThat( decodedReportEntry ).isNotNull();
-            assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
-            assertThat( decodedReportEntry.getSourceText() ).isEqualTo( reportEntry.getSourceText() );
-            assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
-            assertThat( decodedReportEntry.getNameText() ).isEqualTo( reportEntry.getNameText() );
-            assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
-            assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
-            assertThat( decodedReportEntry.getStackTraceWriter() ).isNull();
-
-            decodedReportEntry = EventConsumerThread.newReportEntry( reportEntry.getSourceName(),
-                reportEntry.getSourceText(), reportEntry.getName(), reportEntry.getNameText(), reportEntry.getGroup(),
-                reportEntry.getMessage(), null, exceptionMessage, smartStackTrace, null );
-
-            assertThat( decodedReportEntry ).isNotNull();
-            assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
-            assertThat( decodedReportEntry.getSourceText() ).isEqualTo( reportEntry.getSourceText() );
-            assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
-            assertThat( decodedReportEntry.getNameText() ).isEqualTo( reportEntry.getNameText() );
-            assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
-            assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
-            assertThat( decodedReportEntry.getElapsed() ).isNull();
-            assertThat( decodedReportEntry.getStackTraceWriter() ).isNotNull();
-            assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() )
-                .isEqualTo( exceptionMessage );
-            assertThat( decodedReportEntry.getStackTraceWriter().smartTrimmedStackTrace() )
-                .isEqualTo( smartStackTrace );
-            assertThat( decodedReportEntry.getStackTraceWriter().writeTraceToString() )
-                .isNull();
-
-            decodedReportEntry = EventConsumerThread.newReportEntry( reportEntry.getSourceName(),
-                reportEntry.getSourceText(), reportEntry.getName(), reportEntry.getNameText(), reportEntry.getGroup(),
-                reportEntry.getMessage(), 1003, exceptionMessage, smartStackTrace, null );
-
-            assertThat( decodedReportEntry ).isNotNull();
-            assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
-            assertThat( decodedReportEntry.getSourceText() ).isEqualTo( reportEntry.getSourceText() );
-            assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
-            assertThat( decodedReportEntry.getNameText() ).isEqualTo( reportEntry.getNameText() );
-            assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
-            assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
-            assertThat( decodedReportEntry.getElapsed() ).isEqualTo( 1003 );
-            assertThat( decodedReportEntry.getStackTraceWriter() ).isNotNull();
-            assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() )
-                .isEqualTo( exceptionMessage );
-            assertThat( decodedReportEntry.getStackTraceWriter().smartTrimmedStackTrace() )
-                .isEqualTo( smartStackTrace );
-            assertThat( decodedReportEntry.getStackTraceWriter().writeTraceToString() )
-                .isNull();
-
-            decodedReportEntry = EventConsumerThread.newReportEntry( reportEntry.getSourceName(),
-                reportEntry.getSourceText(), reportEntry.getName(), reportEntry.getNameText(), reportEntry.getGroup(),
-                reportEntry.getMessage(), 1003, exceptionMessage, smartStackTrace, stackTrace );
-
-            assertThat( decodedReportEntry ).isNotNull();
-            assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
-            assertThat( decodedReportEntry.getSourceText() ).isEqualTo( reportEntry.getSourceText() );
-            assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
-            assertThat( decodedReportEntry.getNameText() ).isEqualTo( reportEntry.getNameText() );
-            assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
-            assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
-            assertThat( decodedReportEntry.getElapsed() ).isEqualTo( 1003 );
-            assertThat( decodedReportEntry.getStackTraceWriter() ).isNotNull();
-            assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() ).isNotNull();
-            assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() )
-                    .isEqualTo( exceptionMessage );
-            assertThat( decodedReportEntry.getStackTraceWriter().smartTrimmedStackTrace() )
-                    .isEqualTo( smartStackTrace );
-            assertThat( decodedReportEntry.getStackTraceWriter().writeTraceToString() ).isEqualTo( stackTrace );
-            assertThat( decodedReportEntry.getStackTraceWriter().writeTrimmedTraceToString() ).isEqualTo( stackTrace );
-
-            decodedReportEntry = EventConsumerThread.newReportEntry( reportEntry.getSourceName(),
-                reportEntry.getSourceText(), reportEntry.getName(), reportEntry.getNameText(), reportEntry.getGroup(),
-                reportEntry.getMessage(), 1003, exceptionMessage, smartStackTrace, trimmedStackTrace );
-
-            assertThat( decodedReportEntry ).isNotNull();
-            assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
-            assertThat( decodedReportEntry.getSourceText() ).isEqualTo( reportEntry.getSourceText() );
-            assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
-            assertThat( decodedReportEntry.getNameText() ).isEqualTo( reportEntry.getNameText() );
-            assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
-            assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
-            assertThat( decodedReportEntry.getElapsed() ).isEqualTo( 1003 );
-            assertThat( decodedReportEntry.getStackTraceWriter() ).isNotNull();
-            assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() ).isNotNull();
-            assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() )
-                    .isEqualTo( exceptionMessage );
-            assertThat( decodedReportEntry.getStackTraceWriter().smartTrimmedStackTrace() )
-                    .isEqualTo( smartStackTrace );
-            assertThat( decodedReportEntry.getStackTraceWriter().writeTraceToString() ).isEqualTo( trimmedStackTrace );
-            assertThat( decodedReportEntry.getStackTraceWriter().writeTrimmedTraceToString() )
-                    .isEqualTo( trimmedStackTrace );
-        }
-
-        @Test
         public void shouldSendByeEvent() throws Exception
         {
             Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder =
-                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+            EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
             encoder.bye();
             String read = new String( out.toByteArray(), UTF_8 );
 
@@ -325,8 +181,7 @@ public class ForkedProcessEventNotifierTest
         public void shouldSendStopOnNextTestEvent() throws Exception
         {
             Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder =
-                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+            EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
             encoder.stopOnNextTest();
             String read = new String( out.toByteArray(), UTF_8 );
 
@@ -390,8 +245,7 @@ public class ForkedProcessEventNotifierTest
             when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder =
-                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+            EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
             encoder.testFailed( reportEntry, true );
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
@@ -421,8 +275,7 @@ public class ForkedProcessEventNotifierTest
         public void shouldSendNextTestEvent() throws Exception
         {
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder =
-                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+            EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
             encoder.acquireNextTest();
             String read = new String( out.toByteArray(), UTF_8 );
 
@@ -456,8 +309,7 @@ public class ForkedProcessEventNotifierTest
         public void testConsole() throws Exception
         {
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder =
-                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+            EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
             encoder.consoleInfoLog( "msg" );
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
@@ -487,8 +339,7 @@ public class ForkedProcessEventNotifierTest
         public void testError() throws Exception
         {
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder =
-                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+            EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
             encoder.consoleErrorLog( "msg" );
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
@@ -518,8 +369,7 @@ public class ForkedProcessEventNotifierTest
         public void testErrorWithException() throws Exception
         {
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder =
-                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+            EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
             Throwable throwable = new Throwable( "msg" );
             encoder.consoleErrorLog( throwable );
 
@@ -531,7 +381,7 @@ public class ForkedProcessEventNotifierTest
             notifier.setConsoleErrorListener( listener );
 
             EH eventHandler = new EH();
-            CountdownCloseable countdown = new CountdownCloseable( mock( Closeable.class ), 0 );
+            CountdownCloseable countdown = new CountdownCloseable( mock( Closeable.class ), 1 );
             ConsoleLoggerMock logger = new ConsoleLoggerMock( false, false, false, false );
             ForkNodeArgumentsMock arguments = new ForkNodeArgumentsMock( logger, new File( "" ) );
             try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, arguments ) )
@@ -552,8 +402,7 @@ public class ForkedProcessEventNotifierTest
         {
             final Stream out = Stream.newStream();
 
-            LegacyMasterProcessChannelEncoder encoder =
-                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+            EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
             StackTraceWriter stackTraceWriter = new DeserializedStacktraceWriter( "1", "2", "3" );
             encoder.consoleErrorLog( stackTraceWriter, false );
 
@@ -585,8 +434,7 @@ public class ForkedProcessEventNotifierTest
         {
             final Stream out = Stream.newStream();
 
-            LegacyMasterProcessChannelEncoder encoder =
-                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+            EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
             encoder.consoleDebugLog( "msg" );
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
@@ -623,8 +471,7 @@ public class ForkedProcessEventNotifierTest
         {
             final Stream out = Stream.newStream();
 
-            LegacyMasterProcessChannelEncoder encoder =
-                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+            EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
             encoder.consoleWarningLog( "msg" );
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
@@ -655,7 +502,7 @@ public class ForkedProcessEventNotifierTest
         {
             final Stream out = Stream.newStream();
             WritableBufferedByteChannel wChannel = newBufferedChannel( out );
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( wChannel );
+            EventChannelEncoder encoder = new EventChannelEncoder( wChannel );
             encoder.stdOut( "msg", false );
             wChannel.close();
 
@@ -688,7 +535,7 @@ public class ForkedProcessEventNotifierTest
         {
             final Stream out = Stream.newStream();
             WritableBufferedByteChannel wChannel = newBufferedChannel( out );
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( wChannel );
+            EventChannelEncoder encoder = new EventChannelEncoder( wChannel );
             encoder.stdOut( "", false );
             wChannel.close();
 
@@ -721,7 +568,7 @@ public class ForkedProcessEventNotifierTest
         {
             final Stream out = Stream.newStream();
             WritableBufferedByteChannel wChannel = newBufferedChannel( out );
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( wChannel );
+            EventChannelEncoder encoder = new EventChannelEncoder( wChannel );
             encoder.stdOut( null, false );
             wChannel.close();
 
@@ -754,7 +601,7 @@ public class ForkedProcessEventNotifierTest
         {
             final Stream out = Stream.newStream();
             WritableBufferedByteChannel wChannel = newBufferedChannel( out );
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( wChannel );
+            EventChannelEncoder encoder = new EventChannelEncoder( wChannel );
             encoder.stdOut( "", true );
             wChannel.close();
 
@@ -787,7 +634,7 @@ public class ForkedProcessEventNotifierTest
         {
             final Stream out = Stream.newStream();
             WritableBufferedByteChannel wChannel = newBufferedChannel( out );
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( wChannel );
+            EventChannelEncoder encoder = new EventChannelEncoder( wChannel );
             encoder.stdOut( null, true );
             wChannel.close();
 
@@ -820,7 +667,7 @@ public class ForkedProcessEventNotifierTest
         {
             final Stream out = Stream.newStream();
             WritableBufferedByteChannel wChannel = newBufferedChannel( out );
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( wChannel );
+            EventChannelEncoder encoder = new EventChannelEncoder( wChannel );
             encoder.stdErr( "msg", false );
             wChannel.close();
 
@@ -853,8 +700,8 @@ public class ForkedProcessEventNotifierTest
         {
             final Stream out = Stream.newStream();
             WritableBufferedByteChannel wChannel = newBufferedChannel( out );
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( wChannel );
-            encoder.sendSystemProperties( ObjectUtils.systemProps() );
+            EventChannelEncoder encoder = new EventChannelEncoder( wChannel );
+            encoder.systemProperties( ObjectUtils.systemProps() );
             wChannel.close();
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
@@ -918,7 +765,7 @@ public class ForkedProcessEventNotifierTest
             assertThat( logger.debug.peek() )
                 .contains( ":maven-surefire-event:\u000c:abnormal-run:-:" );
 
-            String dump = "Corrupted STDOUT by directly writing to native stream in forked JVM 0.";
+            String dump = "Corrupted channel by directly writing to native stream in forked JVM 0.";
             assertThat( arguments.dumpStreamText )
                 .hasSize( 1 )
                 .contains( format( dump + " Stream '%s'.", ":maven-surefire-event:\u000c:abnormal-run:-:" ) );
@@ -934,8 +781,7 @@ public class ForkedProcessEventNotifierTest
         public void shouldHandleExit() throws Exception
         {
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder =
-                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+            EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
             StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
             when( stackTraceWriter.getThrowable() ).thenReturn( new SafeThrowable( "1" ) );
@@ -1050,10 +896,9 @@ public class ForkedProcessEventNotifierTest
 
             final Stream out = Stream.newStream();
 
-            LegacyMasterProcessChannelEncoder encoder =
-                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+            EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
-            LegacyMasterProcessChannelEncoder.class.getMethod( operation[0], ReportEntry.class, boolean.class )
+            EventChannelEncoder.class.getMethod( operation[0], ReportEntry.class, boolean.class )
                     .invoke( encoder, reportEntry, trim );
 
             ForkedProcessEventNotifier notifier = new ForkedProcessEventNotifier();
@@ -1323,7 +1168,7 @@ public class ForkedProcessEventNotifierTest
         @Override
         public File dumpStreamException( @Nonnull Throwable t )
         {
-            throw new UnsupportedOperationException();
+            return dumpStreamTextFile;
         }
 
         @Override
@@ -1343,6 +1188,18 @@ public class ForkedProcessEventNotifierTest
         {
             return !dumpStreamText.isEmpty() || !logWarningAtEnd.isEmpty();
         }
+
+        @Override
+        public File getEventStreamBinaryFile()
+        {
+            return null;
+        }
+
+        @Override
+        public File getCommandStreamBinaryFile()
+        {
+            return null;
+        }
     }
 
     /**
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/StreamFeederTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/StreamFeederTest.java
index 06cef43..adbb2a7 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/StreamFeederTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/StreamFeederTest.java
@@ -36,7 +36,6 @@ import java.util.Iterator;
 
 import static java.util.Arrays.asList;
 import static org.apache.maven.surefire.api.booter.Command.TEST_SET_FINISHED;
-import static org.apache.maven.surefire.api.booter.MasterProcessCommand.NOOP;
 import static org.apache.maven.surefire.api.booter.MasterProcessCommand.RUN_CLASS;
 import static org.fest.assertions.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.any;
@@ -92,8 +91,8 @@ public class StreamFeederTest
                 {
                     ByteBuffer bb = invocation.getArgument( 0 );
                     bb.flip();
-                    out.write( bb.array() );
-                    return 0;
+                    out.write( bb.array(), 0, bb.limit() );
+                    return bb.limit();
                 }
             } );
 
@@ -104,8 +103,28 @@ public class StreamFeederTest
         streamFeeder.join();
         String commands = out.toString();
 
+        String expected = new StringBuilder()
+            .append( ":maven-surefire-command:" )
+            .append( (char) 13 )
+            .append( ":run-testclass:" )
+            .append( (char) 10 )
+            .append( ":normal-run:" )
+            .append( (char) 5 )
+            .append( ":UTF-8:" )
+            .append( (char) 0 )
+            .append( (char) 0 )
+            .append( (char) 0 )
+            .append( (char) 9 )
+            .append( ":" )
+            .append( "pkg.ATest" )
+            .append( ":" )
+            .append( ":maven-surefire-command:" )
+            .append( (char) 16 )
+            .append( ":testset-finished:" )
+            .toString();
+
         assertThat( commands )
-            .isEqualTo( ":maven-surefire-command:run-testclass:pkg.ATest::maven-surefire-command:testset-finished:" );
+            .isEqualTo( expected );
 
         verify( channel, times( 1 ) )
             .close();
@@ -147,16 +166,4 @@ public class StreamFeederTest
 
         verifyZeroInteractions( logger );
     }
-
-    @Test( expected = IllegalArgumentException.class )
-    public void shouldFailWithoutData()
-    {
-        StreamFeeder.encode( RUN_CLASS );
-    }
-
-    @Test( expected = IllegalArgumentException.class )
-    public void shouldFailWithData()
-    {
-        StreamFeeder.encode( NOOP, "" );
-    }
 }
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java
index da476a7..fa85b9f 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java
@@ -63,6 +63,7 @@ import org.apache.maven.surefire.extensions.StatelessTestsetInfoReporterTest;
 import org.apache.maven.surefire.report.FileReporterTest;
 import org.apache.maven.surefire.report.RunStatisticsTest;
 import org.apache.maven.surefire.spi.SPITest;
+import org.apache.maven.surefire.stream.EventDecoderTest;
 import org.apache.maven.surefire.util.RelocatorTest;
 
 /**
@@ -116,6 +117,7 @@ public class JUnit4SuiteTest extends TestCase
         suite.addTest( new JUnit4TestAdapter( StreamFeederTest.class ) );
         suite.addTest( new JUnit4TestAdapter( E2ETest.class ) );
         suite.addTest( new JUnit4TestAdapter( ThreadedStreamConsumerTest.class ) );
+        suite.addTest( new JUnit4TestAdapter( EventDecoderTest.class ) );
         suite.addTest( new JUnit4TestAdapter( EventConsumerThreadTest.class ) );
         suite.addTest( new JUnit4TestAdapter( ChecksumCalculatorTest.class ) );
         return suite;
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 8caa668..428ab10 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
@@ -26,6 +26,7 @@ import org.apache.maven.plugin.surefire.extensions.SurefireForkNodeFactory;
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.surefire.api.event.ControlByeEvent;
 import org.apache.maven.surefire.api.event.Event;
+import org.apache.maven.surefire.api.fork.ForkNodeArguments;
 import org.apache.maven.surefire.extensions.util.CountdownCloseable;
 import org.junit.Test;
 
@@ -67,6 +68,18 @@ public class ForkChannelTest
         final String sessionId = UUID.randomUUID().toString();
         ForkNodeArguments forkNodeArguments = new ForkNodeArguments()
         {
+            @Override
+            public File getEventStreamBinaryFile()
+            {
+                return null;
+            }
+
+            @Override
+            public File getCommandStreamBinaryFile()
+            {
+                return null;
+            }
+
             @Nonnull
             @Override
             public String getSessionId()
@@ -203,7 +216,7 @@ public class ForkChannelTest
                 byte[] data = new byte[128];
                 int readLength = socket.getInputStream().read( data );
                 String token = new String( data, 0, readLength, US_ASCII );
-                assertThat( token ).isEqualTo( ":maven-surefire-command:noop:" );
+                assertThat( token ).isEqualTo( ":maven-surefire-command:\u0004:noop:" );
                 socket.getOutputStream().write( ":maven-surefire-event:\u0003:bye:".getBytes( US_ASCII ) );
             }
             catch ( IOException e )
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/stream/EventDecoderTest.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/stream/EventDecoderTest.java
new file mode 100644
index 0000000..ccfd3f0
--- /dev/null
+++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/stream/EventDecoderTest.java
@@ -0,0 +1,785 @@
+package org.apache.maven.surefire.stream;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.surefire.api.booter.ForkedProcessEventType;
+import org.apache.maven.surefire.api.event.ConsoleDebugEvent;
+import org.apache.maven.surefire.api.event.ConsoleErrorEvent;
+import org.apache.maven.surefire.api.event.ConsoleInfoEvent;
+import org.apache.maven.surefire.api.event.ConsoleWarningEvent;
+import org.apache.maven.surefire.api.event.ControlByeEvent;
+import org.apache.maven.surefire.api.event.ControlNextTestEvent;
+import org.apache.maven.surefire.api.event.ControlStopOnNextTestEvent;
+import org.apache.maven.surefire.api.event.Event;
+import org.apache.maven.surefire.api.event.JvmExitErrorEvent;
+import org.apache.maven.surefire.api.event.StandardStreamErrEvent;
+import org.apache.maven.surefire.api.event.StandardStreamErrWithNewLineEvent;
+import org.apache.maven.surefire.api.event.StandardStreamOutEvent;
+import org.apache.maven.surefire.api.event.StandardStreamOutWithNewLineEvent;
+import org.apache.maven.surefire.api.event.SystemPropertyEvent;
+import org.apache.maven.surefire.api.event.TestAssumptionFailureEvent;
+import org.apache.maven.surefire.api.event.TestErrorEvent;
+import org.apache.maven.surefire.api.event.TestFailedEvent;
+import org.apache.maven.surefire.api.event.TestSkippedEvent;
+import org.apache.maven.surefire.api.event.TestStartingEvent;
+import org.apache.maven.surefire.api.event.TestSucceededEvent;
+import org.apache.maven.surefire.api.event.TestsetCompletedEvent;
+import org.apache.maven.surefire.api.event.TestsetStartingEvent;
+import org.apache.maven.surefire.api.fork.ForkNodeArguments;
+import org.apache.maven.surefire.api.report.ReportEntry;
+import org.apache.maven.surefire.api.report.RunMode;
+import org.apache.maven.surefire.api.report.SafeThrowable;
+import org.apache.maven.surefire.api.report.StackTraceWriter;
+import org.apache.maven.surefire.api.stream.AbstractStreamDecoder.Memento;
+import org.apache.maven.surefire.api.stream.AbstractStreamDecoder.Segment;
+import org.apache.maven.surefire.api.stream.SegmentType;
+import org.junit.Test;
+
+import javax.annotation.Nonnull;
+import java.io.File;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
+import java.util.Map;
+
+import static java.lang.Math.min;
+import static java.nio.charset.StandardCharsets.US_ASCII;
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_BYE;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_DEBUG;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_ERROR;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_INFO;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_WARNING;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_JVM_EXIT_ERROR;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_NEXT_TEST;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDERR;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDERR_NEW_LINE;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDOUT;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDOUT_NEW_LINE;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STOP_ON_NEXT_TEST;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_SYSPROPS;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TESTSET_COMPLETED;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TESTSET_STARTING;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_ASSUMPTIONFAILURE;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_ERROR;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_FAILED;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_SKIPPED;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_STARTING;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_SUCCEEDED;
+import static org.apache.maven.surefire.api.report.RunMode.NORMAL_RUN;
+import static org.apache.maven.surefire.api.report.RunMode.RERUN_TEST_AFTER_FAILURE;
+import static org.apache.maven.surefire.api.stream.SegmentType.DATA_INTEGER;
+import static org.apache.maven.surefire.api.stream.SegmentType.DATA_STRING;
+import static org.apache.maven.surefire.api.stream.SegmentType.END_OF_FRAME;
+import static org.apache.maven.surefire.api.stream.SegmentType.RUN_MODE;
+import static org.apache.maven.surefire.api.stream.SegmentType.STRING_ENCODING;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.powermock.api.mockito.PowerMockito.mock;
+import static org.powermock.api.mockito.PowerMockito.when;
+import static org.powermock.reflect.Whitebox.invokeMethod;
+
+/**
+ *
+ */
+public class EventDecoderTest
+{
+    @Test
+    public void shouldMapEventTypes() throws Exception
+    {
+        Map<Segment, ForkedProcessEventType> eventTypes = invokeMethod( EventDecoder.class, "segmentsToEvents" );
+        assertThat( eventTypes )
+            .hasSize( ForkedProcessEventType.values().length );
+    }
+
+    @Test
+    public void shouldMapRunModes() throws Exception
+    {
+        Map<Segment, RunMode> map = invokeMethod( EventDecoder.class, "segmentsToRunModes" );
+
+        assertThat( map )
+            .hasSize( 2 );
+
+        byte[] stream = "normal-run".getBytes( US_ASCII );
+        Segment segment = new Segment( stream, 0, stream.length );
+        assertThat( map.get( segment ) )
+            .isEqualTo( NORMAL_RUN );
+
+        stream = "rerun-test-after-failure".getBytes( US_ASCII );
+        segment = new Segment( stream, 0, stream.length );
+        assertThat( map.get( segment ) )
+            .isEqualTo( RERUN_TEST_AFTER_FAILURE );
+    }
+
+    @Test
+    public void shouldMapEventTypeToSegmentType()
+    {
+        byte[] stream = {};
+        Channel channel = new Channel( stream, 1 );
+        EventDecoder decoder = new EventDecoder( channel, new MockForkNodeArguments() );
+
+        SegmentType[] segmentTypes = decoder.nextSegmentType( BOOTERCODE_BYE );
+        assertThat( segmentTypes )
+            .hasSize( 1 )
+            .containsOnly( END_OF_FRAME );
+
+        segmentTypes = decoder.nextSegmentType( BOOTERCODE_STOP_ON_NEXT_TEST );
+        assertThat( segmentTypes )
+            .hasSize( 1 )
+            .containsOnly( END_OF_FRAME );
+
+        segmentTypes = decoder.nextSegmentType( BOOTERCODE_NEXT_TEST );
+        assertThat( segmentTypes )
+            .hasSize( 1 )
+            .containsOnly( END_OF_FRAME );
+
+        segmentTypes = decoder.nextSegmentType( BOOTERCODE_CONSOLE_ERROR );
+        assertThat( segmentTypes )
+            .hasSize( 5 )
+            .isEqualTo( new SegmentType[] { STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING, END_OF_FRAME } );
+
+        segmentTypes = decoder.nextSegmentType( ForkedProcessEventType.BOOTERCODE_JVM_EXIT_ERROR );
+        assertThat( segmentTypes )
+            .hasSize( 5 )
+            .isEqualTo( new SegmentType[] { STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING, END_OF_FRAME } );
+
+        segmentTypes = decoder.nextSegmentType( BOOTERCODE_CONSOLE_INFO );
+        assertThat( segmentTypes )
+            .hasSize( 3 )
+            .isEqualTo( new SegmentType[] { STRING_ENCODING, DATA_STRING, END_OF_FRAME } );
+
+        segmentTypes = decoder.nextSegmentType( ForkedProcessEventType.BOOTERCODE_CONSOLE_DEBUG );
+        assertThat( segmentTypes )
+            .hasSize( 3 )
+            .isEqualTo( new SegmentType[] { STRING_ENCODING, DATA_STRING, END_OF_FRAME } );
+
+        segmentTypes = decoder.nextSegmentType( ForkedProcessEventType.BOOTERCODE_CONSOLE_WARNING );
+        assertThat( segmentTypes )
+            .hasSize( 3 )
+            .isEqualTo( new SegmentType[] { STRING_ENCODING, DATA_STRING, END_OF_FRAME } );
+
+        segmentTypes = decoder.nextSegmentType( BOOTERCODE_STDOUT );
+        assertThat( segmentTypes )
+            .hasSize( 4 )
+            .isEqualTo( new SegmentType[] { RUN_MODE, STRING_ENCODING, DATA_STRING, END_OF_FRAME } );
+
+        segmentTypes = decoder.nextSegmentType( ForkedProcessEventType.BOOTERCODE_STDOUT_NEW_LINE );
+        assertThat( segmentTypes )
+            .hasSize( 4 )
+            .isEqualTo( new SegmentType[] { RUN_MODE, STRING_ENCODING, DATA_STRING, END_OF_FRAME } );
+
+        segmentTypes = decoder.nextSegmentType( ForkedProcessEventType.BOOTERCODE_STDERR );
+        assertThat( segmentTypes )
+            .hasSize( 4 )
+            .isEqualTo( new SegmentType[] { RUN_MODE, STRING_ENCODING, DATA_STRING, END_OF_FRAME } );
+
+        segmentTypes = decoder.nextSegmentType( ForkedProcessEventType.BOOTERCODE_STDERR_NEW_LINE );
+        assertThat( segmentTypes )
+            .hasSize( 4 )
+            .isEqualTo( new SegmentType[] { RUN_MODE, STRING_ENCODING, DATA_STRING, END_OF_FRAME } );
+
+        segmentTypes = decoder.nextSegmentType( BOOTERCODE_SYSPROPS );
+        assertThat( segmentTypes )
+            .hasSize( 5 )
+            .isEqualTo( new SegmentType[] { RUN_MODE, STRING_ENCODING, DATA_STRING, DATA_STRING, END_OF_FRAME } );
+
+        segmentTypes = decoder.nextSegmentType( BOOTERCODE_TESTSET_STARTING );
+        assertThat( segmentTypes )
+            .hasSize( 13 )
+            .isEqualTo( new SegmentType[] {
+                RUN_MODE, STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING, DATA_STRING, DATA_STRING,
+                DATA_STRING, DATA_INTEGER, DATA_STRING, DATA_STRING, DATA_STRING, END_OF_FRAME } );
+
+        segmentTypes = decoder.nextSegmentType( ForkedProcessEventType.BOOTERCODE_TESTSET_COMPLETED );
+        assertThat( segmentTypes )
+            .hasSize( 13 )
+            .isEqualTo( new SegmentType[] { RUN_MODE, STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING,
+                DATA_STRING, DATA_STRING, DATA_STRING, DATA_INTEGER, DATA_STRING, DATA_STRING, DATA_STRING,
+                END_OF_FRAME } );
+
+        segmentTypes = decoder.nextSegmentType( ForkedProcessEventType.BOOTERCODE_TEST_STARTING );
+        assertThat( segmentTypes )
+            .hasSize( 13 )
+            .isEqualTo( new SegmentType[] { RUN_MODE, STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING,
+                DATA_STRING, DATA_STRING, DATA_STRING, DATA_INTEGER, DATA_STRING, DATA_STRING, DATA_STRING,
+                END_OF_FRAME } );
+
+        segmentTypes = decoder.nextSegmentType( BOOTERCODE_TEST_SUCCEEDED );
+        assertThat( segmentTypes )
+            .hasSize( 13 )
+            .isEqualTo( new SegmentType[] { RUN_MODE, STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING,
+                DATA_STRING, DATA_STRING, DATA_STRING, DATA_INTEGER, DATA_STRING, DATA_STRING, DATA_STRING,
+                END_OF_FRAME } );
+
+        segmentTypes = decoder.nextSegmentType( BOOTERCODE_TEST_FAILED );
+        assertThat( segmentTypes )
+            .hasSize( 13 )
+            .isEqualTo( new SegmentType[] { RUN_MODE, STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING,
+                DATA_STRING, DATA_STRING, DATA_STRING, DATA_INTEGER, DATA_STRING, DATA_STRING, DATA_STRING,
+                END_OF_FRAME } );
+
+        segmentTypes = decoder.nextSegmentType( ForkedProcessEventType.BOOTERCODE_TEST_SKIPPED );
+        assertThat( segmentTypes )
+            .hasSize( 13 )
+            .isEqualTo( new SegmentType[] { RUN_MODE, STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING,
+                DATA_STRING, DATA_STRING, DATA_STRING, DATA_INTEGER, DATA_STRING, DATA_STRING, DATA_STRING,
+                END_OF_FRAME } );
+
+        segmentTypes = decoder.nextSegmentType( ForkedProcessEventType.BOOTERCODE_TEST_ERROR );
+        assertThat( segmentTypes )
+            .hasSize( 13 )
+            .isEqualTo( new SegmentType[] { RUN_MODE, STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING,
+                DATA_STRING, DATA_STRING, DATA_STRING, DATA_INTEGER, DATA_STRING, DATA_STRING, DATA_STRING,
+                END_OF_FRAME } );
+
+        segmentTypes = decoder.nextSegmentType( ForkedProcessEventType.BOOTERCODE_TEST_ASSUMPTIONFAILURE );
+        assertThat( segmentTypes )
+            .hasSize( 13 )
+            .isEqualTo( new SegmentType[] { RUN_MODE, STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING,
+                DATA_STRING, DATA_STRING, DATA_STRING, DATA_INTEGER, DATA_STRING, DATA_STRING, DATA_STRING,
+                END_OF_FRAME } );
+    }
+
+    @Test
+    public void shouldCreateEvent() throws Exception
+    {
+        byte[] stream = {};
+        Channel channel = new Channel( stream, 1 );
+        EventDecoder decoder = new EventDecoder( channel, new MockForkNodeArguments() );
+
+        Event event = decoder.toMessage( BOOTERCODE_BYE, NORMAL_RUN, decoder.new Memento() );
+        assertThat( event )
+            .isInstanceOf( ControlByeEvent.class );
+
+        event = decoder.toMessage( BOOTERCODE_STOP_ON_NEXT_TEST, NORMAL_RUN, decoder.new Memento() );
+        assertThat( event )
+            .isInstanceOf( ControlStopOnNextTestEvent.class );
+
+        event = decoder.toMessage( BOOTERCODE_NEXT_TEST, NORMAL_RUN, decoder.new Memento() );
+        assertThat( event )
+            .isInstanceOf( ControlNextTestEvent.class );
+
+        Memento memento = decoder.new Memento();
+        memento.getData().addAll( asList( "1", "2", "3" ) );
+        event = decoder.toMessage( BOOTERCODE_CONSOLE_ERROR, NORMAL_RUN, memento );
+        assertThat( event )
+            .isInstanceOf( ConsoleErrorEvent.class );
+        ConsoleErrorEvent consoleErrorEvent = (ConsoleErrorEvent) event;
+        assertThat( consoleErrorEvent.getStackTraceWriter().getThrowable().getLocalizedMessage() )
+            .isEqualTo( "1" );
+        assertThat( consoleErrorEvent.getStackTraceWriter().smartTrimmedStackTrace() )
+            .isEqualTo( "2" );
+        assertThat( consoleErrorEvent.getStackTraceWriter().writeTraceToString() )
+            .isEqualTo( "3" );
+
+        memento = decoder.new Memento();
+        memento.getData().addAll( asList( null, null, null ) );
+        event = decoder.toMessage( BOOTERCODE_CONSOLE_ERROR, NORMAL_RUN, memento );
+        assertThat( event )
+            .isInstanceOf( ConsoleErrorEvent.class );
+        consoleErrorEvent = (ConsoleErrorEvent) event;
+        assertThat( consoleErrorEvent.getStackTraceWriter() )
+            .isNull();
+
+        memento = decoder.new Memento();
+        memento.getData().addAll( asList( "1", "2", "3" ) );
+        event = decoder.toMessage( BOOTERCODE_JVM_EXIT_ERROR, NORMAL_RUN, memento );
+        assertThat( event )
+            .isInstanceOf( JvmExitErrorEvent.class );
+        JvmExitErrorEvent jvmExitErrorEvent = (JvmExitErrorEvent) event;
+        assertThat( jvmExitErrorEvent.getStackTraceWriter().getThrowable().getLocalizedMessage() )
+            .isEqualTo( "1" );
+        assertThat( jvmExitErrorEvent.getStackTraceWriter().smartTrimmedStackTrace() )
+            .isEqualTo( "2" );
+        assertThat( jvmExitErrorEvent.getStackTraceWriter().writeTraceToString() )
+            .isEqualTo( "3" );
+
+        memento = decoder.new Memento();
+        memento.getData().addAll( asList( null, null, null ) );
+        event = decoder.toMessage( BOOTERCODE_JVM_EXIT_ERROR, NORMAL_RUN, memento );
+        assertThat( event )
+            .isInstanceOf( JvmExitErrorEvent.class );
+        jvmExitErrorEvent = (JvmExitErrorEvent) event;
+        assertThat( jvmExitErrorEvent.getStackTraceWriter() )
+            .isNull();
+
+        memento = decoder.new Memento();
+        memento.getData().addAll( singletonList( "m" ) );
+        event = decoder.toMessage( BOOTERCODE_CONSOLE_INFO, NORMAL_RUN, memento );
+        assertThat( event ).isInstanceOf( ConsoleInfoEvent.class );
+        assertThat( ( (ConsoleInfoEvent) event ).getMessage() ).isEqualTo( "m" );
+
+        memento = decoder.new Memento();
+        memento.getData().addAll( singletonList( "" ) );
+        event = decoder.toMessage( BOOTERCODE_CONSOLE_WARNING, NORMAL_RUN, memento );
+        assertThat( event ).isInstanceOf( ConsoleWarningEvent.class );
+        assertThat( ( (ConsoleWarningEvent) event ).getMessage() ).isEmpty();
+
+        memento = decoder.new Memento();
+        memento.getData().addAll( singletonList( null ) );
+        event = decoder.toMessage( BOOTERCODE_CONSOLE_DEBUG, NORMAL_RUN, memento );
+        assertThat( event ).isInstanceOf( ConsoleDebugEvent.class );
+        assertThat( ( (ConsoleDebugEvent) event ).getMessage() ).isNull();
+
+        memento = decoder.new Memento();
+        memento.getData().addAll( singletonList( "m" ) );
+        event = decoder.toMessage( BOOTERCODE_STDOUT, NORMAL_RUN, memento );
+        assertThat( event ).isInstanceOf( StandardStreamOutEvent.class );
+        assertThat( ( (StandardStreamOutEvent) event ).getMessage() ).isEqualTo( "m" );
+        assertThat( ( (StandardStreamOutEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
+
+        memento = decoder.new Memento();
+        memento.getData().addAll( singletonList( null ) );
+        event = decoder.toMessage( BOOTERCODE_STDOUT_NEW_LINE, RERUN_TEST_AFTER_FAILURE, memento );
+        assertThat( event ).isInstanceOf( StandardStreamOutWithNewLineEvent.class );
+        assertThat( ( (StandardStreamOutWithNewLineEvent) event ).getMessage() ).isNull();
+        assertThat( ( (StandardStreamOutWithNewLineEvent) event ).getRunMode() ).isEqualTo( RERUN_TEST_AFTER_FAILURE );
+
+        memento = decoder.new Memento();
+        memento.getData().addAll( singletonList( null ) );
+        event = decoder.toMessage( BOOTERCODE_STDERR, RERUN_TEST_AFTER_FAILURE, memento );
+        assertThat( event ).isInstanceOf( StandardStreamErrEvent.class );
+        assertThat( ( (StandardStreamErrEvent) event ).getMessage() ).isNull();
+        assertThat( ( (StandardStreamErrEvent) event ).getRunMode() ).isEqualTo( RERUN_TEST_AFTER_FAILURE );
+
+        memento = decoder.new Memento();
+        memento.getData().addAll( singletonList( "abc" ) );
+        event = decoder.toMessage( BOOTERCODE_STDERR_NEW_LINE, NORMAL_RUN, memento );
+        assertThat( event ).isInstanceOf( StandardStreamErrWithNewLineEvent.class );
+        assertThat( ( (StandardStreamErrWithNewLineEvent) event ).getMessage() ).isEqualTo( "abc" );
+        assertThat( ( (StandardStreamErrWithNewLineEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
+
+        memento = decoder.new Memento();
+        memento.getData().addAll( asList( "key", "value" ) );
+        event = decoder.toMessage( BOOTERCODE_SYSPROPS, NORMAL_RUN, memento );
+        assertThat( event ).isInstanceOf( SystemPropertyEvent.class );
+        assertThat( ( (SystemPropertyEvent) event ).getKey() ).isEqualTo( "key" );
+        assertThat( ( (SystemPropertyEvent) event ).getValue() ).isEqualTo( "value" );
+        assertThat( ( (SystemPropertyEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
+
+        memento = decoder.new Memento();
+        memento.getData().addAll( asList( "source", "sourceText", "name", "nameText", "group", "message", 5,
+            "traceMessage", "smartTrimmedStackTrace", "stackTrace" ) );
+        event = decoder.toMessage( BOOTERCODE_TESTSET_STARTING, NORMAL_RUN, memento );
+        assertThat( event ).isInstanceOf( TestsetStartingEvent.class );
+        assertThat( ( (TestsetStartingEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
+        assertThat( ( (TestsetStartingEvent) event ).getReportEntry().getSourceName() ).isEqualTo( "source" );
+        assertThat( ( (TestsetStartingEvent) event ).getReportEntry().getSourceText() ).isEqualTo( "sourceText" );
+        assertThat( ( (TestsetStartingEvent) event ).getReportEntry().getName() ).isEqualTo( "name" );
+        assertThat( ( (TestsetStartingEvent) event ).getReportEntry().getNameText() ).isEqualTo( "nameText" );
+        assertThat( ( (TestsetStartingEvent) event ).getReportEntry().getGroup() ).isEqualTo( "group" );
+        assertThat( ( (TestsetStartingEvent) event ).getReportEntry().getMessage() ).isEqualTo( "message" );
+        assertThat( ( (TestsetStartingEvent) event ).getReportEntry().getElapsed() ).isEqualTo( 5 );
+        assertThat( ( (TestsetStartingEvent) event )
+            .getReportEntry().getStackTraceWriter().getThrowable().getLocalizedMessage() )
+            .isEqualTo( "traceMessage" );
+        assertThat( ( (TestsetStartingEvent) event ).getReportEntry().getStackTraceWriter().smartTrimmedStackTrace() )
+            .isEqualTo( "smartTrimmedStackTrace" );
+        assertThat( ( (TestsetStartingEvent) event ).getReportEntry().getStackTraceWriter().writeTraceToString() )
+            .isEqualTo( "stackTrace" );
+
+        memento = decoder.new Memento();
+        memento.getData().addAll( asList( "source", "sourceText", "name", "nameText", "group", null, 5,
+            "traceMessage", "smartTrimmedStackTrace", "stackTrace" ) );
+        event = decoder.toMessage( BOOTERCODE_TESTSET_COMPLETED, NORMAL_RUN, memento );
+        assertThat( event ).isInstanceOf( TestsetCompletedEvent.class );
+        assertThat( ( (TestsetCompletedEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
+        assertThat( ( (TestsetCompletedEvent) event ).getReportEntry().getSourceName() ).isEqualTo( "source" );
+        assertThat( ( (TestsetCompletedEvent) event ).getReportEntry().getSourceText() ).isEqualTo( "sourceText" );
+        assertThat( ( (TestsetCompletedEvent) event ).getReportEntry().getName() ).isEqualTo( "name" );
+        assertThat( ( (TestsetCompletedEvent) event ).getReportEntry().getNameText() ).isEqualTo( "nameText" );
+        assertThat( ( (TestsetCompletedEvent) event ).getReportEntry().getGroup() ).isEqualTo( "group" );
+        assertThat( ( (TestsetCompletedEvent) event ).getReportEntry().getMessage() ).isNull();
+        assertThat( ( (TestsetCompletedEvent) event ).getReportEntry().getElapsed() ).isEqualTo( 5 );
+        assertThat( ( (TestsetCompletedEvent) event )
+            .getReportEntry().getStackTraceWriter().getThrowable().getLocalizedMessage() )
+            .isEqualTo( "traceMessage" );
+        assertThat( ( (TestsetCompletedEvent) event ).getReportEntry().getStackTraceWriter().smartTrimmedStackTrace() )
+            .isEqualTo( "smartTrimmedStackTrace" );
+        assertThat( ( (TestsetCompletedEvent) event ).getReportEntry().getStackTraceWriter().writeTraceToString() )
+            .isEqualTo( "stackTrace" );
+
+        memento = decoder.new Memento();
+        memento.getData().addAll( asList( "source", "sourceText", "name", "nameText", "group", "message", 5,
+            null, "smartTrimmedStackTrace", "stackTrace" ) );
+        event = decoder.toMessage( BOOTERCODE_TEST_STARTING, NORMAL_RUN, memento );
+        assertThat( event ).isInstanceOf( TestStartingEvent.class );
+        assertThat( ( (TestStartingEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
+        assertThat( ( (TestStartingEvent) event ).getReportEntry().getSourceName() ).isEqualTo( "source" );
+        assertThat( ( (TestStartingEvent) event ).getReportEntry().getSourceText() ).isEqualTo( "sourceText" );
+        assertThat( ( (TestStartingEvent) event ).getReportEntry().getName() ).isEqualTo( "name" );
+        assertThat( ( (TestStartingEvent) event ).getReportEntry().getNameText() ).isEqualTo( "nameText" );
+        assertThat( ( (TestStartingEvent) event ).getReportEntry().getGroup() ).isEqualTo( "group" );
+        assertThat( ( (TestStartingEvent) event ).getReportEntry().getMessage() ).isEqualTo( "message" );
+        assertThat( ( (TestStartingEvent) event ).getReportEntry().getElapsed() ).isEqualTo( 5 );
+        assertThat( ( (TestStartingEvent) event )
+            .getReportEntry().getStackTraceWriter().getThrowable().getLocalizedMessage() )
+            .isNull();
+        assertThat( ( (TestStartingEvent) event ).getReportEntry().getStackTraceWriter().smartTrimmedStackTrace() )
+            .isEqualTo( "smartTrimmedStackTrace" );
+        assertThat( ( (TestStartingEvent) event ).getReportEntry().getStackTraceWriter().writeTraceToString() )
+            .isEqualTo( "stackTrace" );
+
+        memento = decoder.new Memento();
+        memento.getData()
+            .addAll( asList( "source", "sourceText", "name", "nameText", "group", "message", 5, null, null, null ) );
+        event = decoder.toMessage( BOOTERCODE_TEST_SUCCEEDED, NORMAL_RUN, memento );
+        assertThat( event ).isInstanceOf( TestSucceededEvent.class );
+        assertThat( ( (TestSucceededEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
+        assertThat( ( (TestSucceededEvent) event ).getReportEntry().getSourceName() ).isEqualTo( "source" );
+        assertThat( ( (TestSucceededEvent) event ).getReportEntry().getSourceText() ).isEqualTo( "sourceText" );
+        assertThat( ( (TestSucceededEvent) event ).getReportEntry().getName() ).isEqualTo( "name" );
+        assertThat( ( (TestSucceededEvent) event ).getReportEntry().getNameText() ).isEqualTo( "nameText" );
+        assertThat( ( (TestSucceededEvent) event ).getReportEntry().getGroup() ).isEqualTo( "group" );
+        assertThat( ( (TestSucceededEvent) event ).getReportEntry().getMessage() ).isEqualTo( "message" );
+        assertThat( ( (TestSucceededEvent) event ).getReportEntry().getElapsed() ).isEqualTo( 5 );
+        assertThat( ( (TestSucceededEvent) event ).getReportEntry().getStackTraceWriter() ).isNull();
+
+        memento = decoder.new Memento();
+        memento.getData().addAll( asList( "source", null, "name", null, "group", null, 5,
+            "traceMessage", "smartTrimmedStackTrace", "stackTrace" ) );
+        event = decoder.toMessage( BOOTERCODE_TEST_FAILED, RERUN_TEST_AFTER_FAILURE, memento );
+        assertThat( event ).isInstanceOf( TestFailedEvent.class );
+        assertThat( ( (TestFailedEvent) event ).getRunMode() ).isEqualTo( RERUN_TEST_AFTER_FAILURE );
+        assertThat( ( (TestFailedEvent) event ).getReportEntry().getSourceName() ).isEqualTo( "source" );
+        assertThat( ( (TestFailedEvent) event ).getReportEntry().getSourceText() ).isNull();
+        assertThat( ( (TestFailedEvent) event ).getReportEntry().getName() ).isEqualTo( "name" );
+        assertThat( ( (TestFailedEvent) event ).getReportEntry().getNameText() ).isNull();
+        assertThat( ( (TestFailedEvent) event ).getReportEntry().getGroup() ).isEqualTo( "group" );
+        assertThat( ( (TestFailedEvent) event ).getReportEntry().getMessage() ).isNull();
+        assertThat( ( (TestFailedEvent) event ).getReportEntry().getElapsed() ).isEqualTo( 5 );
+        assertThat( ( (TestFailedEvent) event )
+            .getReportEntry().getStackTraceWriter().getThrowable().getLocalizedMessage() )
+            .isEqualTo( "traceMessage" );
+        assertThat( ( (TestFailedEvent) event ).getReportEntry().getStackTraceWriter().smartTrimmedStackTrace() )
+            .isEqualTo( "smartTrimmedStackTrace" );
+        assertThat( ( (TestFailedEvent) event ).getReportEntry().getStackTraceWriter().writeTraceToString() )
+            .isEqualTo( "stackTrace" );
+
+        memento = decoder.new Memento();
+        memento.getData().addAll( asList( "source", null, "name", null, null, null, 5, null, null, "stackTrace" ) );
+        event = decoder.toMessage( BOOTERCODE_TEST_SKIPPED, NORMAL_RUN, memento );
+        assertThat( event ).isInstanceOf( TestSkippedEvent.class );
+        assertThat( ( (TestSkippedEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
+        assertThat( ( (TestSkippedEvent) event ).getReportEntry().getSourceName() ).isEqualTo( "source" );
+        assertThat( ( (TestSkippedEvent) event ).getReportEntry().getSourceText() ).isNull();
+        assertThat( ( (TestSkippedEvent) event ).getReportEntry().getName() ).isEqualTo( "name" );
+        assertThat( ( (TestSkippedEvent) event ).getReportEntry().getNameText() ).isNull();
+        assertThat( ( (TestSkippedEvent) event ).getReportEntry().getGroup() ).isNull();
+        assertThat( ( (TestSkippedEvent) event ).getReportEntry().getMessage() ).isNull();
+        assertThat( ( (TestSkippedEvent) event ).getReportEntry().getElapsed() ).isEqualTo( 5 );
+        assertThat( ( (TestSkippedEvent) event )
+            .getReportEntry().getStackTraceWriter().getThrowable().getLocalizedMessage() )
+            .isNull();
+        assertThat( ( (TestSkippedEvent) event )
+            .getReportEntry().getStackTraceWriter().smartTrimmedStackTrace() )
+            .isNull();
+        assertThat( ( (TestSkippedEvent) event )
+            .getReportEntry().getStackTraceWriter().writeTraceToString() )
+            .isEqualTo( "stackTrace" );
+
+        memento = decoder.new Memento();
+        memento.getData()
+            .addAll( asList( "source", null, "name", "nameText", null, null, 0, null, null, "stackTrace" ) );
+        event = decoder.toMessage( BOOTERCODE_TEST_ERROR, NORMAL_RUN, memento );
+        assertThat( event ).isInstanceOf( TestErrorEvent.class );
+        assertThat( ( (TestErrorEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
+        assertThat( ( (TestErrorEvent) event ).getReportEntry().getSourceName() ).isEqualTo( "source" );
+        assertThat( ( (TestErrorEvent) event ).getReportEntry().getSourceText() ).isNull();
+        assertThat( ( (TestErrorEvent) event ).getReportEntry().getName() ).isEqualTo( "name" );
+        assertThat( ( (TestErrorEvent) event ).getReportEntry().getNameText() ).isEqualTo( "nameText" );
+        assertThat( ( (TestErrorEvent) event ).getReportEntry().getGroup() ).isNull();
+        assertThat( ( (TestErrorEvent) event ).getReportEntry().getMessage() ).isNull();
+        assertThat( ( (TestErrorEvent) event ).getReportEntry().getElapsed() ).isZero();
+        assertThat( ( (TestErrorEvent) event )
+            .getReportEntry().getStackTraceWriter().getThrowable().getLocalizedMessage() )
+            .isNull();
+        assertThat( ( (TestErrorEvent) event )
+            .getReportEntry().getStackTraceWriter().smartTrimmedStackTrace() )
+            .isNull();
+        assertThat( ( (TestErrorEvent) event )
+            .getReportEntry().getStackTraceWriter().writeTraceToString() )
+            .isEqualTo( "stackTrace" );
+
+        memento = decoder.new Memento();
+        memento.getData().addAll( asList( "source", null, "name", null, "group", null, 5, null, null, "stackTrace" ) );
+        event = decoder.toMessage( BOOTERCODE_TEST_ASSUMPTIONFAILURE, NORMAL_RUN, memento );
+        assertThat( event ).isInstanceOf( TestAssumptionFailureEvent.class );
+        assertThat( ( (TestAssumptionFailureEvent) event ).getRunMode() ).isEqualTo( NORMAL_RUN );
+        assertThat( ( (TestAssumptionFailureEvent) event ).getReportEntry().getSourceName() ).isEqualTo( "source" );
+        assertThat( ( (TestAssumptionFailureEvent) event ).getReportEntry().getSourceText() ).isNull();
+        assertThat( ( (TestAssumptionFailureEvent) event ).getReportEntry().getName() ).isEqualTo( "name" );
+        assertThat( ( (TestAssumptionFailureEvent) event ).getReportEntry().getNameText() ).isNull();
+        assertThat( ( (TestAssumptionFailureEvent) event ).getReportEntry().getGroup() ).isEqualTo( "group" );
+        assertThat( ( (TestAssumptionFailureEvent) event ).getReportEntry().getMessage() ).isNull();
+        assertThat( ( (TestAssumptionFailureEvent) event ).getReportEntry().getElapsed() ).isEqualTo( 5 );
+        assertThat( ( (TestAssumptionFailureEvent) event )
+            .getReportEntry().getStackTraceWriter().getThrowable().getLocalizedMessage() )
+            .isNull();
+        assertThat( ( (TestAssumptionFailureEvent) event )
+            .getReportEntry().getStackTraceWriter().smartTrimmedStackTrace() )
+            .isNull();
+        assertThat( ( (TestAssumptionFailureEvent) event )
+            .getReportEntry().getStackTraceWriter().writeTraceToString() )
+            .isEqualTo( "stackTrace" );
+    }
+
+    @Test
+    public void shouldRecognizeEmptyStream4ReportEntry()
+    {
+        ReportEntry reportEntry = EventDecoder.newReportEntry( "", "", "", "", "", "", null, "", "", "" );
+        assertThat( reportEntry ).isNotNull();
+        assertThat( reportEntry.getStackTraceWriter() ).isNotNull();
+        assertThat( reportEntry.getStackTraceWriter().smartTrimmedStackTrace() ).isEmpty();
+        assertThat( reportEntry.getStackTraceWriter().writeTraceToString() ).isEmpty();
+        assertThat( reportEntry.getStackTraceWriter().writeTrimmedTraceToString() ).isEmpty();
+        assertThat( reportEntry.getSourceName() ).isEmpty();
+        assertThat( reportEntry.getSourceText() ).isEmpty();
+        assertThat( reportEntry.getName() ).isEmpty();
+        assertThat( reportEntry.getNameText() ).isEmpty();
+        assertThat( reportEntry.getGroup() ).isEmpty();
+        assertThat( reportEntry.getNameWithGroup() ).isEmpty();
+        assertThat( reportEntry.getMessage() ).isEmpty();
+        assertThat( reportEntry.getElapsed() ).isNull();
+    }
+
+    @Test
+    @SuppressWarnings( "checkstyle:magicnumber" )
+    public void testCreatingReportEntry()
+    {
+        final String exceptionMessage = "msg";
+        final String smartStackTrace = "MyTest:86 >> Error";
+        final String stackTrace = "Exception: msg\ntrace line 1\ntrace line 2";
+        final String trimmedStackTrace = "trace line 1\ntrace line 2";
+
+        SafeThrowable safeThrowable = new SafeThrowable( exceptionMessage );
+        StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
+        when( stackTraceWriter.getThrowable() ).thenReturn( safeThrowable );
+        when( stackTraceWriter.smartTrimmedStackTrace() ).thenReturn( smartStackTrace );
+        when( stackTraceWriter.writeTrimmedTraceToString() ).thenReturn( trimmedStackTrace );
+        when( stackTraceWriter.writeTraceToString() ).thenReturn( stackTrace );
+
+        ReportEntry reportEntry = mock( ReportEntry.class );
+        when( reportEntry.getElapsed() ).thenReturn( 102 );
+        when( reportEntry.getGroup() ).thenReturn( "this group" );
+        when( reportEntry.getMessage() ).thenReturn( "skipped test" );
+        when( reportEntry.getName() ).thenReturn( "my test" );
+        when( reportEntry.getNameText() ).thenReturn( "my display name" );
+        when( reportEntry.getNameWithGroup() ).thenReturn( "name with group" );
+        when( reportEntry.getSourceName() ).thenReturn( "pkg.MyTest" );
+        when( reportEntry.getSourceText() ).thenReturn( "test class display name" );
+        when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
+
+        ReportEntry decodedReportEntry = EventDecoder.newReportEntry( reportEntry.getSourceName(),
+            reportEntry.getSourceText(), reportEntry.getName(), reportEntry.getNameText(), reportEntry.getGroup(),
+            reportEntry.getMessage(), null, null, null, null );
+
+        assertThat( decodedReportEntry ).isNotNull();
+        assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
+        assertThat( decodedReportEntry.getSourceText() ).isEqualTo( reportEntry.getSourceText() );
+        assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
+        assertThat( decodedReportEntry.getNameText() ).isEqualTo( reportEntry.getNameText() );
+        assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
+        assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
+        assertThat( decodedReportEntry.getStackTraceWriter() ).isNull();
+
+        decodedReportEntry = EventDecoder.newReportEntry( reportEntry.getSourceName(),
+            reportEntry.getSourceText(), reportEntry.getName(), reportEntry.getNameText(), reportEntry.getGroup(),
+            reportEntry.getMessage(), null, exceptionMessage, smartStackTrace, null );
+
+        assertThat( decodedReportEntry ).isNotNull();
+        assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
+        assertThat( decodedReportEntry.getSourceText() ).isEqualTo( reportEntry.getSourceText() );
+        assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
+        assertThat( decodedReportEntry.getNameText() ).isEqualTo( reportEntry.getNameText() );
+        assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
+        assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
+        assertThat( decodedReportEntry.getElapsed() ).isNull();
+        assertThat( decodedReportEntry.getStackTraceWriter() ).isNotNull();
+        assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() )
+            .isEqualTo( exceptionMessage );
+        assertThat( decodedReportEntry.getStackTraceWriter().smartTrimmedStackTrace() )
+            .isEqualTo( smartStackTrace );
+        assertThat( decodedReportEntry.getStackTraceWriter().writeTraceToString() )
+            .isNull();
+
+        decodedReportEntry = EventDecoder.newReportEntry( reportEntry.getSourceName(),
+            reportEntry.getSourceText(), reportEntry.getName(), reportEntry.getNameText(), reportEntry.getGroup(),
+            reportEntry.getMessage(), 1003, exceptionMessage, smartStackTrace, null );
+
+        assertThat( decodedReportEntry ).isNotNull();
+        assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
+        assertThat( decodedReportEntry.getSourceText() ).isEqualTo( reportEntry.getSourceText() );
+        assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
+        assertThat( decodedReportEntry.getNameText() ).isEqualTo( reportEntry.getNameText() );
+        assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
+        assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
+        assertThat( decodedReportEntry.getElapsed() ).isEqualTo( 1003 );
+        assertThat( decodedReportEntry.getStackTraceWriter() ).isNotNull();
+        assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() )
+            .isEqualTo( exceptionMessage );
+        assertThat( decodedReportEntry.getStackTraceWriter().smartTrimmedStackTrace() )
+            .isEqualTo( smartStackTrace );
+        assertThat( decodedReportEntry.getStackTraceWriter().writeTraceToString() )
+            .isNull();
+
+        decodedReportEntry = EventDecoder.newReportEntry( reportEntry.getSourceName(),
+            reportEntry.getSourceText(), reportEntry.getName(), reportEntry.getNameText(), reportEntry.getGroup(),
+            reportEntry.getMessage(), 1003, exceptionMessage, smartStackTrace, stackTrace );
+
+        assertThat( decodedReportEntry ).isNotNull();
+        assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
+        assertThat( decodedReportEntry.getSourceText() ).isEqualTo( reportEntry.getSourceText() );
+        assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
+        assertThat( decodedReportEntry.getNameText() ).isEqualTo( reportEntry.getNameText() );
+        assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
+        assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
+        assertThat( decodedReportEntry.getElapsed() ).isEqualTo( 1003 );
+        assertThat( decodedReportEntry.getStackTraceWriter() ).isNotNull();
+        assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() ).isNotNull();
+        assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() )
+            .isEqualTo( exceptionMessage );
+        assertThat( decodedReportEntry.getStackTraceWriter().smartTrimmedStackTrace() )
+            .isEqualTo( smartStackTrace );
+        assertThat( decodedReportEntry.getStackTraceWriter().writeTraceToString() ).isEqualTo( stackTrace );
+        assertThat( decodedReportEntry.getStackTraceWriter().writeTrimmedTraceToString() ).isEqualTo( stackTrace );
+
+        decodedReportEntry = EventDecoder.newReportEntry( reportEntry.getSourceName(),
+            reportEntry.getSourceText(), reportEntry.getName(), reportEntry.getNameText(), reportEntry.getGroup(),
+            reportEntry.getMessage(), 1003, exceptionMessage, smartStackTrace, trimmedStackTrace );
+
+        assertThat( decodedReportEntry ).isNotNull();
+        assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
+        assertThat( decodedReportEntry.getSourceText() ).isEqualTo( reportEntry.getSourceText() );
+        assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
+        assertThat( decodedReportEntry.getNameText() ).isEqualTo( reportEntry.getNameText() );
+        assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
+        assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
+        assertThat( decodedReportEntry.getElapsed() ).isEqualTo( 1003 );
+        assertThat( decodedReportEntry.getStackTraceWriter() ).isNotNull();
+        assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() ).isNotNull();
+        assertThat( decodedReportEntry.getStackTraceWriter().getThrowable().getMessage() )
+            .isEqualTo( exceptionMessage );
+        assertThat( decodedReportEntry.getStackTraceWriter().smartTrimmedStackTrace() )
+            .isEqualTo( smartStackTrace );
+        assertThat( decodedReportEntry.getStackTraceWriter().writeTraceToString() ).isEqualTo( trimmedStackTrace );
+        assertThat( decodedReportEntry.getStackTraceWriter().writeTrimmedTraceToString() )
+            .isEqualTo( trimmedStackTrace );
+    }
+
+    private static class Channel implements ReadableByteChannel
+    {
+        private final byte[] bytes;
+        private final int chunkSize;
+        protected int i;
+
+        Channel( byte[] bytes, int chunkSize )
+        {
+            this.bytes = bytes;
+            this.chunkSize = chunkSize;
+        }
+
+        @Override
+        public int read( ByteBuffer dst )
+        {
+            if ( i == bytes.length )
+            {
+                return -1;
+            }
+            else if ( dst.hasRemaining() )
+            {
+                int length = min( min( chunkSize, bytes.length - i ), dst.remaining() ) ;
+                dst.put( bytes, i, length );
+                i += length;
+                return length;
+            }
+            else
+            {
+                return 0;
+            }
+        }
+
+        @Override
+        public boolean isOpen()
+        {
+            return false;
+        }
+
+        @Override
+        public void close()
+        {
+        }
+    }
+
+    private static class MockForkNodeArguments implements ForkNodeArguments
+    {
+        @Nonnull
+        @Override
+        public String getSessionId()
+        {
+            return null;
+        }
+
+        @Override
+        public int getForkChannelId()
+        {
+            return 0;
+        }
+
+        @Nonnull
+        @Override
+        public File dumpStreamText( @Nonnull String text )
+        {
+            return null;
+        }
+
+        @Nonnull
+        @Override
+        public File dumpStreamException( @Nonnull Throwable t )
+        {
+            return null;
+        }
+
+        @Override
+        public void logWarningAtEnd( @Nonnull String text )
+        {
+        }
+
+        @Nonnull
+        @Override
+        public ConsoleLogger getConsoleLogger()
+        {
+            return null;
+        }
+
+        @Override
+        public File getEventStreamBinaryFile()
+        {
+            return null;
+        }
+
+        @Override
+        public File getCommandStreamBinaryFile()
+        {
+            return null;
+        }
+    }
+
+}
diff --git a/maven-surefire-plugin/src/site/fml/faq.fml b/maven-surefire-plugin/src/site/fml/faq.fml
index e5aaada..2464d08 100644
--- a/maven-surefire-plugin/src/site/fml/faq.fml
+++ b/maven-surefire-plugin/src/site/fml/faq.fml
@@ -138,7 +138,7 @@ under the License.
       </answer>
     </faq>
     <faq id="corruptedstream">
-      <question>Corrupted STDOUT by directly writing to native stream in forked JVM</question>
+      <question>Corrupted channel by directly writing to native stream in forked JVM</question>
       <answer>
         <p>
         If your tests use native library which prints to STDOUT this warning message appears because the library
@@ -149,7 +149,7 @@ under the License.
         This warning message appears if you use <em>FileDescriptor.out</em> or JVM prints GC summary.
         <br/>
         In that case the warning is printed
-        <em>"Corrupted STDOUT by directly writing to native stream in forked JVM"</em>, and a dump file can be found
+        <em>"Corrupted channel by directly writing to native stream in forked JVM"</em>, and a dump file can be found
         in Reports directory.
         <br/>
         If debug level is enabled then messages of corrupted stream appear in the console.
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/Constants.java b/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/Constants.java
index bcb7b17..639aba8 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/Constants.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/Constants.java
@@ -29,8 +29,10 @@ import static java.nio.charset.StandardCharsets.UTF_8;
  */
 public final class Constants
 {
-    public static final String MAGIC_NUMBER = "maven-surefire-event";
-    public static final byte[] MAGIC_NUMBER_BYTES = MAGIC_NUMBER.getBytes( US_ASCII );
+    private static final String MAGIC_NUMBER_FOR_EVENTS = "maven-surefire-event";
+    public static final String MAGIC_NUMBER_FOR_COMMANDS = "maven-surefire-command";
+    public static final byte[] MAGIC_NUMBER_FOR_EVENTS_BYTES = MAGIC_NUMBER_FOR_EVENTS.getBytes( US_ASCII );
+    public static final byte[] MAGIC_NUMBER_FOR_COMMANDS_BYTES = MAGIC_NUMBER_FOR_COMMANDS.getBytes( US_ASCII );
     public static final Charset DEFAULT_STREAM_ENCODING = UTF_8;
     public static final byte[] DEFAULT_STREAM_ENCODING_BYTES = UTF_8.name().getBytes( US_ASCII );
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/DumpErrorSingleton.java b/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/DumpErrorSingleton.java
index b54bb2e..791dae5 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/DumpErrorSingleton.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/DumpErrorSingleton.java
@@ -40,6 +40,7 @@ public final class DumpErrorSingleton
 
     private File dumpFile;
     private File dumpStreamFile;
+    private File binaryDumpStreamFile;
 
     private DumpErrorSingleton()
     {
@@ -54,36 +55,50 @@ public final class DumpErrorSingleton
     {
         dumpFile = createDumpFile( reportsDir, dumpFileName );
         dumpStreamFile = createDumpStreamFile( reportsDir, dumpFileName );
+        String fileNameWithoutExtension =
+            dumpFileName.contains( "." ) ? dumpFileName.substring( 0, dumpFileName.lastIndexOf( '.' ) ) : dumpFileName;
+        binaryDumpStreamFile = newDumpFile( reportsDir, fileNameWithoutExtension + "-commands.bin" );
     }
 
-    public synchronized void dumpException( Throwable t, String msg )
+    public synchronized File dumpException( Throwable t, String msg )
     {
         DumpFileUtils.dumpException( t, msg == null ? "null" : msg, dumpFile );
+        return dumpFile;
     }
 
-    public synchronized void dumpException( Throwable t )
+    public synchronized File dumpException( Throwable t )
     {
         DumpFileUtils.dumpException( t, dumpFile );
+        return dumpFile;
     }
 
-    public synchronized void dumpText( String msg )
+    public synchronized File dumpText( String msg )
     {
         DumpFileUtils.dumpText( msg == null ? "null" : msg, dumpFile );
+        return dumpFile;
     }
 
-    public synchronized void dumpStreamException( Throwable t, String msg )
+    public synchronized File dumpStreamException( Throwable t, String msg )
     {
         DumpFileUtils.dumpException( t, msg == null ? "null" : msg, dumpStreamFile );
+        return dumpStreamFile;
     }
 
-    public synchronized void dumpStreamException( Throwable t )
+    public synchronized File dumpStreamException( Throwable t )
     {
         DumpFileUtils.dumpException( t, dumpStreamFile );
+        return dumpStreamFile;
     }
 
-    public synchronized void dumpStreamText( String msg )
+    public synchronized File dumpStreamText( String msg )
     {
         DumpFileUtils.dumpText( msg == null ? "null" : msg, dumpStreamFile );
+        return dumpStreamFile;
+    }
+
+    public File getCommandStreamBinaryFile()
+    {
+        return binaryDumpStreamFile;
     }
 
     private File createDumpFile( File reportsDir, String dumpFileName )
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/ForkingRunListener.java b/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/ForkingRunListener.java
index 7cf1dea..169f358 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/ForkingRunListener.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/ForkingRunListener.java
@@ -71,7 +71,7 @@ public class ForkingRunListener
     @Override
     public void testSetCompleted( TestSetReportEntry report )
     {
-        target.sendSystemProperties( report.getSystemProperties() );
+        target.systemProperties( report.getSystemProperties() );
         target.testSetCompleted( report, trim );
     }
 
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/MasterProcessChannelEncoder.java b/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/MasterProcessChannelEncoder.java
index e8ad45b..cb543f6 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/MasterProcessChannelEncoder.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/MasterProcessChannelEncoder.java
@@ -32,15 +32,11 @@ import java.util.Map;
  */
 public interface MasterProcessChannelEncoder
 {
-    MasterProcessChannelEncoder asRerunMode();
-
-    MasterProcessChannelEncoder asNormalMode();
-
     boolean checkError();
 
     void onJvmExit();
 
-    void sendSystemProperties( Map<String, String> sysProps );
+    void systemProperties( Map<String, String> sysProps );
 
     void testSetStarting( ReportEntry reportEntry, boolean trimStackTraces );
 
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/MasterProcessCommand.java b/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/MasterProcessCommand.java
index 64c392d..f492339 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/MasterProcessCommand.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/MasterProcessCommand.java
@@ -19,6 +19,12 @@ package org.apache.maven.surefire.api.booter;
  * under the License.
  */
 
+import org.apache.maven.surefire.api.stream.AbstractStreamDecoder.Segment;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static java.nio.charset.StandardCharsets.US_ASCII;
 import static java.util.Objects.requireNonNull;
 
 /**
@@ -29,24 +35,39 @@ import static java.util.Objects.requireNonNull;
  */
 public enum MasterProcessCommand
 {
-    RUN_CLASS( String.class ),
-    TEST_SET_FINISHED( Void.class ),
-    SKIP_SINCE_NEXT_TEST( Void.class ),
-    SHUTDOWN( String.class ),
+    RUN_CLASS( "run-testclass", String.class ),
+    TEST_SET_FINISHED( "testset-finished", Void.class ),
+    SKIP_SINCE_NEXT_TEST( "skip-since-next-test", Void.class ),
+    SHUTDOWN( "shutdown", String.class ),
 
     /** To tell a forked process that the master process is still alive. Repeated after 10 seconds. */
-    NOOP( Void.class ),
-    BYE_ACK( Void.class );
+    NOOP( "noop", Void.class ),
+    BYE_ACK( "bye-ack", Void.class );
 
-    public static final String MAGIC_NUMBER = "maven-surefire-command";
+    // due to have fast and thread-safe Map
+    public static final Map<Segment, MasterProcessCommand> COMMAND_TYPES = segmentsToCmds();
 
+    private final String opcode;
+    private final byte[] opcodeBinary;
     private final Class<?> dataType;
 
-    MasterProcessCommand( Class<?> dataType )
+    MasterProcessCommand( String opcode, Class<?> dataType )
     {
+        this.opcode = requireNonNull( opcode, "value cannot be null" );
+        opcodeBinary = opcode.getBytes( US_ASCII );
         this.dataType = requireNonNull( dataType, "dataType cannot be null" );
     }
 
+    public byte[] getOpcodeBinary()
+    {
+        return opcodeBinary;
+    }
+
+    public int getOpcodeLength()
+    {
+        return opcodeBinary.length;
+    }
+
     public Class<?> getDataType()
     {
         return dataType;
@@ -56,4 +77,21 @@ public enum MasterProcessCommand
     {
         return dataType != Void.class;
     }
+
+    @Override
+    public String toString()
+    {
+        return opcode;
+    }
+
+    private static Map<Segment, MasterProcessCommand> segmentsToCmds()
+    {
+        Map<Segment, MasterProcessCommand> commands = new HashMap<>();
+        for ( MasterProcessCommand command : MasterProcessCommand.values() )
+        {
+            byte[] array = command.toString().getBytes( US_ASCII );
+            commands.put( new Segment( array, 0, array.length ), command );
+        }
+        return commands;
+    }
 }
diff --git a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkNodeArguments.java b/surefire-api/src/main/java/org/apache/maven/surefire/api/fork/ForkNodeArguments.java
similarity index 92%
rename from surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkNodeArguments.java
rename to surefire-api/src/main/java/org/apache/maven/surefire/api/fork/ForkNodeArguments.java
index 4ab6cc3..7d20e0e 100644
--- a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkNodeArguments.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/api/fork/ForkNodeArguments.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.extensions;
+package org.apache.maven.surefire.api.fork;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -54,4 +54,8 @@ public interface ForkNodeArguments
 
     @Nonnull
     ConsoleLogger getConsoleLogger();
+
+    File getEventStreamBinaryFile();
+
+    File getCommandStreamBinaryFile();
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/api/report/RunMode.java b/surefire-api/src/main/java/org/apache/maven/surefire/api/report/RunMode.java
index 7329f9c..dbdd580 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/api/report/RunMode.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/api/report/RunMode.java
@@ -19,6 +19,11 @@ package org.apache.maven.surefire.api.report;
  * under the License.
  */
 
+import org.apache.maven.surefire.api.stream.AbstractStreamDecoder.Segment;
+
+import java.util.HashMap;
+import java.util.Map;
+
 import static java.nio.charset.StandardCharsets.US_ASCII;
 
 /**
@@ -35,6 +40,9 @@ public enum RunMode
     RERUN_TEST_AFTER_FAILURE( "rerun-test-after-failure" );
     //todo add here RERUN_TESTSET, see https://github.com/apache/maven-surefire/pull/221
 
+    // due to have fast and thread-safe Map
+    public static final Map<Segment, RunMode> RUN_MODES = segmentsToRunModes();
+
     private final String runmode;
     private final byte[] runmodeBinary;
 
@@ -53,4 +61,15 @@ public enum RunMode
     {
         return runmodeBinary;
     }
+
+    private static Map<Segment, RunMode> segmentsToRunModes()
+    {
+        Map<Segment, RunMode> runModes = new HashMap<>();
+        for ( RunMode runMode : RunMode.values() )
+        {
+            byte[] array = runMode.getRunmodeBinary();
+            runModes.put( new Segment( array, 0, array.length ), runMode );
+        }
+        return runModes;
+    }
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/api/stream/AbstractStreamDecoder.java b/surefire-api/src/main/java/org/apache/maven/surefire/api/stream/AbstractStreamDecoder.java
new file mode 100644
index 0000000..862820c
--- /dev/null
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/api/stream/AbstractStreamDecoder.java
@@ -0,0 +1,735 @@
+package org.apache.maven.surefire.api.stream;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.surefire.api.fork.ForkNodeArguments;
+import org.apache.maven.surefire.api.report.RunMode;
+
+import javax.annotation.Nonnegative;
+import javax.annotation.Nonnull;
+import java.io.EOFException;
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CoderResult;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+import static java.nio.charset.CodingErrorAction.REPLACE;
+import static java.nio.charset.StandardCharsets.US_ASCII;
+import static java.util.Arrays.copyOf;
+import static org.apache.maven.surefire.api.booter.Constants.DEFAULT_STREAM_ENCODING;
+import static org.apache.maven.surefire.api.stream.AbstractStreamDecoder.StreamReadStatus.OVERFLOW;
+import static org.apache.maven.surefire.api.stream.AbstractStreamDecoder.StreamReadStatus.UNDERFLOW;
+import static org.apache.maven.surefire.shared.lang3.StringUtils.isBlank;
+
+/**
+ * @param <M> message object
+ * @param <MT> enum describing the meaning of the message
+ * @param <ST> enum for segment type
+ */
+public abstract class AbstractStreamDecoder<M, MT extends Enum<MT>, ST extends Enum<ST>> implements AutoCloseable
+{
+    public static final int BUFFER_SIZE = 1024;
+
+    private static final String PRINTABLE_JVM_NATIVE_STREAM = "Listening for transport dt_socket at address:";
+
+    private static final String[] JVM_ERROR_PATTERNS = {
+        "could not create the java virtual machine", "error occurred during initialization", // of VM, of boot layer
+        "error:", // general errors
+        "could not reserve enough space", "could not allocate", "unable to allocate", // memory errors
+        "java.lang.module.findexception" // JPMS errors
+    };
+
+    private static final byte[] DEFAULT_STREAM_ENCODING_BYTES = DEFAULT_STREAM_ENCODING.name().getBytes( US_ASCII );
+
+    private static final int NO_POSITION = -1;
+    private static final int DELIMITER_LENGTH = 1;
+    private static final int BYTE_LENGTH = 1;
+    private static final int INT_LENGTH = 4;
+
+    private final ReadableByteChannel channel;
+    private final ForkNodeArguments arguments;
+    private final Map<Segment, MT> messageTypes;
+    private final ConsoleLogger logger;
+
+    protected AbstractStreamDecoder( @Nonnull ReadableByteChannel channel,
+                                     @Nonnull ForkNodeArguments arguments,
+                                     @Nonnull Map<Segment, MT> messageTypes )
+    {
+        this.channel = channel;
+        this.arguments = arguments;
+        this.messageTypes = messageTypes;
+        logger = arguments.getConsoleLogger();
+    }
+
+    public abstract M decode( @Nonnull Memento memento ) throws MalformedChannelException, IOException;
+
+    @Nonnull
+    protected abstract byte[] getEncodedMagicNumber();
+
+    @Nonnull
+    protected abstract ST[] nextSegmentType( @Nonnull MT messageType );
+
+    @Nonnull
+    protected abstract M toMessage( @Nonnull MT messageType, RunMode runMode, @Nonnull Memento memento )
+        throws MalformedFrameException;
+
+    @Nonnull
+    protected final ForkNodeArguments getArguments()
+    {
+        return arguments;
+    }
+
+    protected void debugStream( byte[] array, int position, int remaining )
+    {
+    }
+
+    protected MT readMessageType( @Nonnull Memento memento ) throws IOException, MalformedFrameException
+    {
+        byte[] header = getEncodedMagicNumber();
+        int readCount = DELIMITER_LENGTH + header.length + DELIMITER_LENGTH + BYTE_LENGTH + DELIMITER_LENGTH;
+        read( memento, readCount );
+        checkHeader( memento );
+        return messageTypes.get( readSegment( memento ) );
+    }
+
+    @Nonnull
+    @SuppressWarnings( "checkstyle:magicnumber" )
+    protected Segment readSegment( @Nonnull Memento memento ) throws IOException, MalformedFrameException
+    {
+        int readCount = readByte( memento ) & 0xff;
+        read( memento, readCount + DELIMITER_LENGTH );
+        ByteBuffer bb = memento.getByteBuffer();
+        Segment segment = new Segment( bb.array(), bb.arrayOffset() + bb.position(), readCount );
+        bb.position( bb.position() + readCount );
+        checkDelimiter( memento );
+        return segment;
+    }
+
+    @Nonnull
+    @SuppressWarnings( "checkstyle:magicnumber" )
+    protected Charset readCharset( @Nonnull Memento memento ) throws IOException, MalformedFrameException
+    {
+        int length = readByte( memento ) & 0xff;
+        read( memento, length + DELIMITER_LENGTH );
+        ByteBuffer bb = memento.getByteBuffer();
+        byte[] array = bb.array();
+        int offset = bb.arrayOffset() + bb.position();
+        bb.position( bb.position() + length );
+        boolean isDefaultEncoding = false;
+        if ( length == DEFAULT_STREAM_ENCODING_BYTES.length )
+        {
+            isDefaultEncoding = true;
+            for ( int i = 0; i < length; i++ )
+            {
+                isDefaultEncoding &= DEFAULT_STREAM_ENCODING_BYTES[i] == array[offset + i];
+            }
+        }
+
+        try
+        {
+            Charset charset =
+                isDefaultEncoding
+                    ? DEFAULT_STREAM_ENCODING
+                    : Charset.forName( new String( array, offset, length, US_ASCII ) );
+
+            checkDelimiter( memento );
+            return charset;
+        }
+        catch ( IllegalArgumentException e )
+        {
+            throw new MalformedFrameException( memento.getLine().getPositionByteBuffer(), bb.position() );
+        }
+    }
+
+    protected String readString( @Nonnull Memento memento ) throws IOException, MalformedFrameException
+    {
+        memento.getCharBuffer().clear();
+        int readCount = readInt( memento );
+        if ( readCount < 0 )
+        {
+            throw new MalformedFrameException( memento.getLine().getPositionByteBuffer(),
+                memento.getByteBuffer().position() );
+        }
+        read( memento, readCount + DELIMITER_LENGTH );
+
+        final String string;
+        if ( readCount == 0 )
+        {
+            string = "";
+        }
+        else if ( readCount == 1 )
+        {
+            read( memento, 1 );
+            byte oneChar = memento.getByteBuffer().get();
+            string = oneChar == 0 ? null : String.valueOf( (char) oneChar );
+        }
+        else
+        {
+            string = readString( memento, readCount );
+        }
+        read( memento, 1 );
+        checkDelimiter( memento );
+        return string;
+    }
+
+    protected Integer readInteger( @Nonnull Memento memento ) throws IOException, MalformedFrameException
+    {
+        read( memento, BYTE_LENGTH );
+        boolean isNullObject = memento.getByteBuffer().get() == 0;
+        if ( isNullObject )
+        {
+            read( memento, DELIMITER_LENGTH );
+            checkDelimiter( memento );
+            return null;
+        }
+        return readInt( memento );
+    }
+
+    protected byte readByte( @Nonnull Memento memento ) throws IOException, MalformedFrameException
+    {
+        read( memento, BYTE_LENGTH + DELIMITER_LENGTH );
+        byte b = memento.getByteBuffer().get();
+        checkDelimiter( memento );
+        return b;
+    }
+
+    protected int readInt( @Nonnull Memento memento ) throws IOException, MalformedFrameException
+    {
+        read( memento, INT_LENGTH + DELIMITER_LENGTH );
+        int i = memento.getByteBuffer().getInt();
+        checkDelimiter( memento );
+        return i;
+    }
+
+    @SuppressWarnings( "checkstyle:magicnumber" )
+    protected final void checkDelimiter( Memento memento ) throws MalformedFrameException
+    {
+        ByteBuffer bb = memento.bb;
+        if ( ( 0xff & bb.get() ) != ':' )
+        {
+            throw new MalformedFrameException( memento.getLine().getPositionByteBuffer(), bb.position() );
+        }
+    }
+
+    protected final void checkHeader( Memento memento ) throws MalformedFrameException
+    {
+        ByteBuffer bb = memento.bb;
+
+        checkDelimiter( memento );
+
+        int shift = 0;
+        try
+        {
+            byte[] header = getEncodedMagicNumber();
+            for ( int start = bb.arrayOffset() + bb.position(), length = header.length; shift < length; shift++ )
+            {
+                if ( bb.array()[shift + start] != header[shift] )
+                {
+                    throw new MalformedFrameException( memento.getLine().getPositionByteBuffer(),
+                        bb.position() + shift );
+                }
+            }
+        }
+        finally
+        {
+            bb.position( bb.position() + shift );
+        }
+
+        checkDelimiter( memento );
+    }
+
+    protected void checkArguments( RunMode runMode, Memento memento, int expectedDataElements )
+        throws MalformedFrameException
+    {
+        if ( runMode == null )
+        {
+            throw new MalformedFrameException( memento.getLine().getPositionByteBuffer(),
+                memento.getByteBuffer().position() );
+        }
+        checkArguments( memento, expectedDataElements );
+    }
+
+    protected void checkArguments( Memento memento, int expectedDataElements )
+        throws MalformedFrameException
+    {
+        if ( memento.getData().size() != expectedDataElements )
+        {
+            throw new MalformedFrameException( memento.getLine().getPositionByteBuffer(),
+                memento.getByteBuffer().position() );
+        }
+    }
+
+    private String readString( @Nonnull final Memento memento, @Nonnegative final int totalBytes )
+        throws IOException, MalformedFrameException
+    {
+        memento.getDecoder().reset();
+        final CharBuffer output = memento.getCharBuffer();
+        output.clear();
+        final ByteBuffer input = memento.getByteBuffer();
+        final List<String> strings = new ArrayList<>();
+        int countDecodedBytes = 0;
+        for ( boolean endOfInput = false; !endOfInput; )
+        {
+            final int bytesToRead = totalBytes - countDecodedBytes;
+            read( memento, bytesToRead );
+            int bytesToDecode = min( input.remaining(), bytesToRead );
+            final boolean isLastChunk = bytesToDecode == bytesToRead;
+            endOfInput = countDecodedBytes + bytesToDecode >= totalBytes;
+            do
+            {
+                boolean endOfChunk = output.remaining() >= bytesToRead;
+                boolean endOfOutput = isLastChunk && endOfChunk;
+                int readInputBytes = decodeString( memento.getDecoder(), input, output, bytesToDecode, endOfOutput,
+                    memento.getLine().getPositionByteBuffer() );
+                bytesToDecode -= readInputBytes;
+                countDecodedBytes += readInputBytes;
+            }
+            while ( isLastChunk && bytesToDecode > 0 && output.hasRemaining() );
+
+            if ( isLastChunk || !output.hasRemaining() )
+            {
+                strings.add( output.flip().toString() );
+                output.clear();
+            }
+        }
+
+        memento.getDecoder().reset();
+        output.clear();
+
+        return toString( strings );
+    }
+
+    private static int decodeString( @Nonnull CharsetDecoder decoder, @Nonnull ByteBuffer input,
+                                     @Nonnull CharBuffer output, @Nonnegative int bytesToDecode,
+                                     boolean endOfInput, @Nonnegative int errorStreamFrom )
+        throws MalformedFrameException
+    {
+        int limit = input.limit();
+        input.limit( input.position() + bytesToDecode );
+
+        CoderResult result = decoder.decode( input, output, endOfInput );
+        if ( result.isError() || result.isMalformed() )
+        {
+            throw new MalformedFrameException( errorStreamFrom, input.position() );
+        }
+
+        int decodedBytes = bytesToDecode - input.remaining();
+        input.limit( limit );
+        return decodedBytes;
+    }
+
+    private static String toString( List<String> strings )
+    {
+        if ( strings.size() == 1 )
+        {
+            return strings.get( 0 );
+        }
+        StringBuilder concatenated = new StringBuilder( strings.size() * BUFFER_SIZE );
+        for ( String s : strings )
+        {
+            concatenated.append( s );
+        }
+        return concatenated.toString();
+    }
+
+    private void printCorruptedStream( Memento memento )
+    {
+        ByteBuffer bb = memento.getByteBuffer();
+        if ( bb.hasRemaining() )
+        {
+            int bytesToWrite = bb.remaining();
+            memento.getLine().write( bb, bb.position(), bytesToWrite );
+            bb.position( bb.position() + bytesToWrite );
+        }
+    }
+
+    /**
+     * Print the last string which has not been finished by a new line character.
+     *
+     * @param memento current memento object
+     */
+    protected final void printRemainingStream( Memento memento )
+    {
+        printCorruptedStream( memento );
+        memento.getLine().printExistingLine();
+        memento.getLine().setCount( 0 );
+    }
+
+    /**
+     *
+     */
+    public static final class Segment
+    {
+        private final byte[] array;
+        private final int fromIndex;
+        private final int length;
+        private final int hashCode;
+
+        public Segment( byte[] array, int fromIndex, int length )
+        {
+            this.array = array;
+            this.fromIndex = fromIndex;
+            this.length = length;
+
+            int hashCode = 0;
+            int i = fromIndex;
+            for ( int loops = length >> 1; loops-- != 0; )
+            {
+                hashCode = 31 * hashCode + array[i++];
+                hashCode = 31 * hashCode + array[i++];
+            }
+            this.hashCode = i == fromIndex + length ? hashCode : 31 * hashCode + array[i];
+        }
+
+        @Override
+        public int hashCode()
+        {
+            return hashCode;
+        }
+
+        @Override
+        public boolean equals( Object obj )
+        {
+            if ( !( obj instanceof Segment ) )
+            {
+                return false;
+            }
+
+            Segment that = (Segment) obj;
+            if ( that.length != length )
+            {
+                return false;
+            }
+
+            for ( int i = 0; i < length; i++ )
+            {
+                if ( that.array[that.fromIndex + i] != array[fromIndex + i] )
+                {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+
+    protected @Nonnull StreamReadStatus read( @Nonnull Memento memento, int recommendedCount ) throws IOException
+    {
+        ByteBuffer buffer = memento.getByteBuffer();
+        if ( buffer.remaining() >= recommendedCount && buffer.limit() != 0 )
+        {
+            return OVERFLOW;
+        }
+        else
+        {
+            if ( buffer.position() != 0 && recommendedCount > buffer.capacity() - buffer.position() )
+            {
+                buffer.compact().flip();
+                memento.getLine().setPositionByteBuffer( 0 );
+            }
+            int mark = buffer.position();
+            buffer.position( buffer.limit() );
+            buffer.limit( min( buffer.position() + recommendedCount, buffer.capacity() ) );
+            boolean isEnd = false;
+            while ( !isEnd && buffer.position() - mark < recommendedCount && buffer.position() < buffer.limit() )
+            {
+                isEnd = channel.read( buffer ) == -1;
+            }
+
+            buffer.limit( buffer.position() );
+            buffer.position( mark );
+            int readBytes = buffer.remaining();
+
+            if ( isEnd && readBytes < recommendedCount )
+            {
+                throw new EOFException();
+            }
+            else
+            {
+                debugStream( buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining() );
+                return readBytes >= recommendedCount ? OVERFLOW : UNDERFLOW;
+            }
+        }
+    }
+
+    /**
+     *
+     */
+    public final class Memento
+    {
+        private CharsetDecoder currentDecoder;
+        private final CharsetDecoder defaultDecoder;
+        private final BufferedStream line = new BufferedStream( 32 );
+        private final List<Object> data = new ArrayList<>();
+        private final CharBuffer cb = CharBuffer.allocate( BUFFER_SIZE );
+        private final ByteBuffer bb = ByteBuffer.allocate( BUFFER_SIZE );
+
+        public Memento()
+        {
+            defaultDecoder = DEFAULT_STREAM_ENCODING.newDecoder()
+                .onMalformedInput( REPLACE )
+                .onUnmappableCharacter( REPLACE );
+            bb.limit( 0 );
+        }
+
+        public void reset()
+        {
+            currentDecoder = null;
+            data.clear();
+        }
+
+        public CharsetDecoder getDecoder()
+        {
+            return currentDecoder == null ? defaultDecoder : currentDecoder;
+        }
+
+        public void setCharset( Charset charset )
+        {
+            if ( charset.name().equals( defaultDecoder.charset().name() ) )
+            {
+                currentDecoder = defaultDecoder;
+            }
+            else
+            {
+                currentDecoder = charset.newDecoder()
+                    .onMalformedInput( REPLACE )
+                    .onUnmappableCharacter( REPLACE );
+            }
+        }
+
+        public BufferedStream getLine()
+        {
+            return line;
+        }
+
+        public List<Object> getData()
+        {
+            return data;
+        }
+
+        public CharBuffer getCharBuffer()
+        {
+            return cb;
+        }
+
+        public ByteBuffer getByteBuffer()
+        {
+            return bb;
+        }
+    }
+
+    /**
+     * This class avoids locking which gains the performance of this decoder.
+     */
+    public final class BufferedStream
+    {
+        private byte[] buffer;
+        private int count;
+        private int positionByteBuffer;
+        private boolean isNewLine;
+
+        BufferedStream( int capacity )
+        {
+            this.buffer = new byte[capacity];
+        }
+
+        public int getPositionByteBuffer()
+        {
+            return positionByteBuffer;
+        }
+
+        public void setPositionByteBuffer( int positionByteBuffer )
+        {
+            this.positionByteBuffer = positionByteBuffer;
+        }
+
+        public void write( ByteBuffer bb, int position, int length )
+        {
+            ensureCapacity( length );
+            byte[] array = bb.array();
+            int pos = bb.arrayOffset() + position;
+            while ( length-- > 0 )
+            {
+                positionByteBuffer++;
+                byte b = array[pos++];
+                if ( b == '\r' || b == '\n' )
+                {
+                    if ( !isNewLine )
+                    {
+                        printExistingLine();
+                        count = 0;
+                    }
+                    isNewLine = true;
+                }
+                else
+                {
+                    buffer[count++] = b;
+                    isNewLine = false;
+                }
+            }
+        }
+
+        public void setCount( int count )
+        {
+            this.count = count;
+        }
+
+        @Override
+        public String toString()
+        {
+            return new String( buffer, 0, count, DEFAULT_STREAM_ENCODING );
+        }
+
+        private boolean isEmpty()
+        {
+            return count == 0;
+        }
+
+        private void ensureCapacity( int addCapacity )
+        {
+            int oldCapacity = buffer.length;
+            int exactCapacity = count + addCapacity;
+            if ( exactCapacity < 0 )
+            {
+                throw new OutOfMemoryError();
+            }
+
+            if ( oldCapacity < exactCapacity )
+            {
+                int newCapacity = oldCapacity << 1;
+                buffer = copyOf( buffer, max( newCapacity, exactCapacity ) );
+            }
+        }
+
+        public void printExistingLine()
+        {
+            if ( isEmpty() )
+            {
+                return;
+            }
+
+            String s = toString();
+            if ( isBlank( s ) )
+            {
+                return;
+            }
+
+            if ( s.contains( PRINTABLE_JVM_NATIVE_STREAM ) )
+            {
+                if ( logger.isDebugEnabled() )
+                {
+                    logger.debug( s );
+                }
+                else if ( logger.isInfoEnabled() )
+                {
+                    logger.info( s );
+                }
+                else
+                {
+                    // In case of debugging forked JVM, see PRINTABLE_JVM_NATIVE_STREAM.
+                    System.out.println( s );
+                }
+            }
+            else
+            {
+                if ( isJvmError( s ) )
+                {
+                    logger.error( s );
+                }
+                else if ( logger.isDebugEnabled() )
+                {
+                    logger.debug( s );
+                }
+
+                String msg = "Corrupted channel by directly writing to native stream in forked JVM "
+                    + arguments.getForkChannelId() + ".";
+                File dumpFile = arguments.dumpStreamText( msg + " Stream '" + s + "'." );
+                String dumpPath = dumpFile.getAbsolutePath();
+                arguments.logWarningAtEnd( msg + " See FAQ web page and the dump file " + dumpPath );
+            }
+        }
+
+        private boolean isJvmError( String line )
+        {
+            String lineLower = line.toLowerCase();
+            for ( String errorPattern : JVM_ERROR_PATTERNS )
+            {
+                if ( lineLower.contains( errorPattern ) )
+                {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
+     *
+     */
+    public static final class MalformedFrameException extends Exception
+    {
+        private final int readFrom;
+        private final int readTo;
+
+        public MalformedFrameException( int readFrom, int readTo )
+        {
+            this.readFrom = readFrom;
+            this.readTo = readTo;
+        }
+
+        public int readFrom()
+        {
+            return readFrom;
+        }
+
+        public int readTo()
+        {
+            return readTo;
+        }
+
+        public boolean hasValidPositions()
+        {
+            return readFrom != NO_POSITION && readTo != NO_POSITION && readTo - readFrom > 0;
+        }
+    }
+
+    /**
+     * Underflow - could not completely read out al bytes in one call.
+     * <br>
+     * Overflow - read all bytes or more
+     * <br>
+     * EOF - end of stream
+     */
+    public enum StreamReadStatus
+    {
+        UNDERFLOW,
+        OVERFLOW,
+        EOF
+    }
+}
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/api/stream/AbstractStreamEncoder.java b/surefire-api/src/main/java/org/apache/maven/surefire/api/stream/AbstractStreamEncoder.java
new file mode 100644
index 0000000..d033958
--- /dev/null
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/api/stream/AbstractStreamEncoder.java
@@ -0,0 +1,191 @@
+package org.apache.maven.surefire.api.stream;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.api.report.RunMode;
+import org.apache.maven.surefire.api.util.internal.WritableBufferedByteChannel;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.WritableByteChannel;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+
+import static java.lang.Math.ceil;
+import static java.nio.CharBuffer.wrap;
+
+/**
+ * The base class of stream encoder.
+ * The type of message is expressed by opcode where the opcode object is described by the generic type {@link E}.
+ * @param <E> type of the message
+ */
+public abstract class AbstractStreamEncoder<E extends Enum<E>>
+{
+    private static final byte BOOLEAN_NON_NULL_OBJECT = (byte) 0xff;
+    private static final byte BOOLEAN_NULL_OBJECT = (byte) 0;
+    private static final byte[] INT_BINARY = new byte[] {0, 0, 0, 0};
+
+    private final WritableByteChannel out;
+
+    public AbstractStreamEncoder( WritableByteChannel out )
+    {
+        this.out = out;
+    }
+
+    @Nonnull
+    protected abstract byte[] getEncodedMagicNumber();
+
+    @Nonnull
+    protected abstract byte[] enumToByteArray( E e );
+
+    @Nonnull
+    protected abstract byte[] getEncodedCharsetName();
+
+    @Nonnull
+    protected abstract Charset getCharset();
+
+    @Nonnull
+    protected abstract CharsetEncoder newCharsetEncoder();
+
+    protected void write( ByteBuffer frame, boolean sendImmediately )
+        throws IOException
+    {
+        if ( !sendImmediately && out instanceof WritableBufferedByteChannel )
+        {
+            ( (WritableBufferedByteChannel) out ).writeBuffered( frame );
+        }
+        else
+        {
+            out.write( frame );
+        }
+    }
+
+    public void encodeHeader( ByteBuffer result, E operation, RunMode runMode )
+    {
+        result.put( (byte) ':' );
+        result.put( getEncodedMagicNumber() );
+        result.put( (byte) ':' );
+        byte[] opcode = enumToByteArray( operation );
+        result.put( (byte) opcode.length );
+        result.put( (byte) ':' );
+        result.put( opcode );
+        result.put( (byte) ':' );
+
+        if ( runMode != null )
+        {
+            byte[] runmode = runMode.getRunmodeBinary();
+            result.put( (byte) runmode.length );
+            result.put( (byte) ':' );
+            result.put( runmode );
+            result.put( (byte) ':' );
+        }
+    }
+
+    public void encodeCharset( ByteBuffer result )
+    {
+        byte[] charsetNameBinary = getEncodedCharsetName();
+        result.put( (byte) charsetNameBinary.length );
+        result.put( (byte) ':' );
+        result.put( charsetNameBinary );
+        result.put( (byte) ':' );
+    }
+
+    public void encodeString( CharsetEncoder encoder, ByteBuffer result, String string )
+    {
+        String nonNullString = nonNull( string );
+
+        int counterPosition = result.position();
+
+        result.put( INT_BINARY ).put( (byte) ':' );
+
+        int msgStart = result.position();
+        encoder.encode( wrap( nonNullString ), result, true );
+        int msgEnd = result.position();
+        int encodedMsgSize = msgEnd - msgStart;
+        result.putInt( counterPosition, encodedMsgSize );
+
+        result.position( msgEnd );
+
+        result.put( (byte) ':' );
+    }
+
+    public void encodeInteger( ByteBuffer result, Integer i )
+    {
+        if ( i == null )
+        {
+            result.put( BOOLEAN_NULL_OBJECT );
+        }
+        else
+        {
+            result.put( BOOLEAN_NON_NULL_OBJECT ).putInt( i );
+        }
+        result.put( (byte) ':' );
+    }
+
+    public void encode( CharsetEncoder encoder, ByteBuffer result, E operation, RunMode runMode, String... messages )
+    {
+        encodeHeader( result, operation, runMode );
+        encodeCharset( result );
+        for ( String message : messages )
+        {
+            encodeString( encoder, result, message );
+        }
+    }
+
+    public int estimateBufferLength( int opcodeLength, RunMode runMode, CharsetEncoder encoder,
+                                     int integersCounter, String... strings )
+    {
+        assert !( encoder == null && strings.length != 0 );
+
+        // one delimiter character ':' + <string> + one delimiter character ':' +
+        // one byte + one delimiter character ':' + <string> + one delimiter character ':'
+        int lengthOfMetadata = 1 + getEncodedMagicNumber().length + 1 + 1 + 1 + opcodeLength + 1;
+
+        if ( runMode != null )
+        {
+            // one byte of length + one delimiter character ':' + <string> + one delimiter character ':'
+            lengthOfMetadata += 1 + 1 + runMode.geRunmode().length() + 1;
+        }
+
+        if ( encoder != null )
+        {
+            // one byte of length + one delimiter character ':' + <string> + one delimiter character ':'
+            lengthOfMetadata += 1 + 1 + encoder.charset().name().length() + 1;
+        }
+
+        // one byte (0x00 if NULL) + 4 bytes for integer + one delimiter character ':'
+        int lengthOfData = ( 1 + 1 + 4 ) * integersCounter;
+
+        for ( String string : strings )
+        {
+            String s = nonNull( string );
+            // 4 bytes of string length + one delimiter character ':' + <string> + one delimiter character ':'
+            lengthOfData += 4 + 1 + (int) ceil( encoder.maxBytesPerChar() * s.length() ) + 1;
+        }
+
+        return lengthOfMetadata + lengthOfData;
+    }
+
+    private static String nonNull( String msg )
+    {
+        return msg == null ? "\u0000" : msg;
+    }
+}
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/Constants.java b/surefire-api/src/main/java/org/apache/maven/surefire/api/stream/MalformedChannelException.java
similarity index 60%
copy from surefire-api/src/main/java/org/apache/maven/surefire/api/booter/Constants.java
copy to surefire-api/src/main/java/org/apache/maven/surefire/api/stream/MalformedChannelException.java
index bcb7b17..cf4468f 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/Constants.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/api/stream/MalformedChannelException.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.api.booter;
+package org.apache.maven.surefire.api.stream;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,18 +19,9 @@ package org.apache.maven.surefire.api.booter;
  * under the License.
  */
 
-import java.nio.charset.Charset;
-
-import static java.nio.charset.StandardCharsets.US_ASCII;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
 /**
  *
  */
-public final class Constants
+public class MalformedChannelException extends Exception
 {
-    public static final String MAGIC_NUMBER = "maven-surefire-event";
-    public static final byte[] MAGIC_NUMBER_BYTES = MAGIC_NUMBER.getBytes( US_ASCII );
-    public static final Charset DEFAULT_STREAM_ENCODING = UTF_8;
-    public static final byte[] DEFAULT_STREAM_ENCODING_BYTES = UTF_8.name().getBytes( US_ASCII );
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/Constants.java b/surefire-api/src/main/java/org/apache/maven/surefire/api/stream/SegmentType.java
similarity index 60%
copy from surefire-api/src/main/java/org/apache/maven/surefire/api/booter/Constants.java
copy to surefire-api/src/main/java/org/apache/maven/surefire/api/stream/SegmentType.java
index bcb7b17..f81d2ce 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/Constants.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/api/stream/SegmentType.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.api.booter;
+package org.apache.maven.surefire.api.stream;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,18 +19,17 @@ package org.apache.maven.surefire.api.booter;
  * under the License.
  */
 
-import java.nio.charset.Charset;
-
-import static java.nio.charset.StandardCharsets.US_ASCII;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
 /**
  *
  */
-public final class Constants
+public enum SegmentType
 {
-    public static final String MAGIC_NUMBER = "maven-surefire-event";
-    public static final byte[] MAGIC_NUMBER_BYTES = MAGIC_NUMBER.getBytes( US_ASCII );
-    public static final Charset DEFAULT_STREAM_ENCODING = UTF_8;
-    public static final byte[] DEFAULT_STREAM_ENCODING_BYTES = UTF_8.name().getBytes( US_ASCII );
+    RUN_MODE,
+    STRING_ENCODING,
+    DATA_STRING,
+    DATA_INTEGER,
+    DATA_INT,
+    DATA_BYTE,
+    DATA_STRING_ARRAY,
+    END_OF_FRAME
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/api/util/internal/DumpFileUtils.java b/surefire-api/src/main/java/org/apache/maven/surefire/api/util/internal/DumpFileUtils.java
index bac68be..b552aa2 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/api/util/internal/DumpFileUtils.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/api/util/internal/DumpFileUtils.java
@@ -52,6 +52,7 @@ public final class DumpFileUtils
      */
     public static synchronized File newDumpFile( File reportsDir, String dumpFileName )
     {
+        reportsDir.mkdirs();
         return new File( reportsDir, dumpFileName );
     }
 
diff --git a/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java b/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java
index 7ac3ba2..c707ed6 100644
--- a/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java
@@ -25,6 +25,8 @@ import org.apache.maven.surefire.api.runorder.ThreadedExecutionSchedulerTest;
 import org.apache.maven.surefire.SpecificTestClassFilterTest;
 import org.apache.maven.surefire.api.booter.ForkingRunListenerTest;
 import org.apache.maven.surefire.api.report.LegacyPojoStackTraceWriterTest;
+import org.apache.maven.surefire.api.stream.AbstractStreamDecoderTest;
+import org.apache.maven.surefire.api.stream.AbstractStreamEncoderTest;
 import org.apache.maven.surefire.api.suite.RunResultTest;
 import org.apache.maven.surefire.api.testset.FundamentalFilterTest;
 import org.apache.maven.surefire.api.testset.ResolvedTestTest;
@@ -68,7 +70,9 @@ import org.junit.runners.Suite;
     ReflectionUtilsTest.class,
     ChannelsReaderTest.class,
     ChannelsWriterTest.class,
-    AsyncSocketTest.class
+    AsyncSocketTest.class,
+    AbstractStreamEncoderTest.class,
+    AbstractStreamDecoderTest.class
 } )
 @RunWith( Suite.class )
 public class JUnit4SuiteTest
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/api/stream/AbstractStreamDecoderTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/api/stream/AbstractStreamDecoderTest.java
new file mode 100644
index 0000000..5ab7ae0
--- /dev/null
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/api/stream/AbstractStreamDecoderTest.java
@@ -0,0 +1,692 @@
+package org.apache.maven.surefire.api.stream;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.surefire.api.booter.Constants;
+import org.apache.maven.surefire.api.booter.ForkedProcessEventType;
+import org.apache.maven.surefire.api.event.Event;
+import org.apache.maven.surefire.api.fork.ForkNodeArguments;
+import org.apache.maven.surefire.api.report.RunMode;
+import org.apache.maven.surefire.api.stream.AbstractStreamDecoder.MalformedFrameException;
+import org.apache.maven.surefire.api.stream.AbstractStreamDecoder.Memento;
+import org.apache.maven.surefire.api.stream.AbstractStreamDecoder.Segment;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import javax.annotation.Nonnull;
+import java.io.EOFException;
+import java.io.File;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.charset.CharsetDecoder;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import static java.lang.Math.min;
+import static java.lang.System.arraycopy;
+import static java.nio.charset.CodingErrorAction.REPLACE;
+import static java.nio.charset.StandardCharsets.ISO_8859_1;
+import static java.nio.charset.StandardCharsets.US_ASCII;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Collections.singletonMap;
+import static org.apache.maven.surefire.api.booter.Constants.DEFAULT_STREAM_ENCODING;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDOUT;
+import static org.apache.maven.surefire.api.stream.SegmentType.END_OF_FRAME;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.powermock.reflect.Whitebox.invokeMethod;
+
+/**
+ * The performance of "get( Integer )" is 13.5 nano seconds on i5/2.6GHz:
+ * <pre>
+ *     {@code
+ *     TreeMap<Integer, ForkedProcessEventType> map = new TreeMap<>();
+ *     map.get( hash );
+ *     }
+ * </pre>
+ *
+ * <br> The performance of getting event type by Segment is 33.7 nano seconds:
+ * <pre>
+ *     {@code
+ *     Map<Segment, ForkedProcessEventType> map = new HashMap<>();
+ *     byte[] array = ForkedProcessEventType.BOOTERCODE_STDOUT.getOpcode().getBytes( UTF_8 );
+ *     map.get( new Segment( array, 0, array.length ) );
+ *     }
+ * </pre>
+ *
+ * <br> The performance of decoder:
+ * <pre>
+ *     {@code
+ *     CharsetDecoder decoder = STREAM_ENCODING.newDecoder()
+ *             .onMalformedInput( REPLACE )
+ *             .onUnmappableCharacter( REPLACE );
+ *     ByteBuffer buffer = ByteBuffer.wrap( ForkedProcessEventType.BOOTERCODE_STDOUT.getOpcode().getBytes( UTF_8 ) );
+ *     CharBuffer chars = CharBuffer.allocate( 100 );
+ *     decoder.reset().decode( buffer, chars, true );
+ *
+ *     String s = chars.flip().toString(); // 37 nanos = CharsetDecoder + toString
+ *
+ *     buffer.clear();
+ *     chars.clear();
+ *
+ *     ForkedProcessEventType.byOpcode( s ); // 65 nanos = CharsetDecoder + toString + byOpcode
+ *     }
+ * </pre>
+ *
+ * <br> The performance of decoding 100 bytes via CharacterDecoder - 71 nano seconds:
+ * <pre>
+ *     {@code
+ *     decoder.reset()
+ *         .decode( buffer, chars, true ); // CharsetDecoder 71 nanos
+ *     chars.flip().toString(); // CharsetDecoder + toString = 91 nanos
+ *     }
+ * </pre>
+ *
+ * <br> The performance of a pure string creation (instead of decoder) - 31.5 nano seconds:
+ * <pre>
+ *     {@code
+ *     byte[] b = {};
+ *     new String( b, UTF_8 );
+ *     }
+ * </pre>
+ *
+ * <br> The performance of CharsetDecoder with empty ByteBuffer:
+ * <pre>
+ *     {@code
+ *     CharsetDecoder + ByteBuffer.allocate( 0 ) makes 11.5 nanos
+ *     CharsetDecoder + ByteBuffer.allocate( 0 ) + toString() makes 16.1 nanos
+ *     }
+ * </pre>
+ */
+@SuppressWarnings( "checkstyle:magicnumber" )
+public class AbstractStreamDecoderTest
+{
+    private static final Map<Segment, ForkedProcessEventType> EVENTS = new HashMap<>();
+
+    private static final String PATTERN1 =
+        "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789";
+
+    private static final String PATTERN2 = "€ab©c";
+
+    private static final byte[] PATTERN2_BYTES =
+        new byte[] {(byte) -30, (byte) -126, (byte) -84, 'a', 'b', (byte) 0xc2, (byte) 0xa9, 'c'};
+
+    @BeforeClass
+    public static void setup()
+    {
+        for ( ForkedProcessEventType event : ForkedProcessEventType.values() )
+        {
+            byte[] array = event.getOpcodeBinary();
+            EVENTS.put( new Segment( array, 0, array.length ), event );
+        }
+    }
+
+    @Test
+    public void shouldDecodeHappyCase() throws Exception
+    {
+        CharsetDecoder decoder = UTF_8.newDecoder().onMalformedInput( REPLACE ).onUnmappableCharacter( REPLACE );
+        ByteBuffer input = ByteBuffer.allocate( 1024 );
+        input.put( PATTERN2_BYTES ).flip();
+        int bytesToDecode = PATTERN2_BYTES.length;
+        CharBuffer output = CharBuffer.allocate( 1024 );
+        int readBytes = invokeMethod( AbstractStreamDecoder.class, "decodeString", decoder, input, output,
+            bytesToDecode, true, 0 );
+
+        assertThat( readBytes )
+            .isEqualTo( bytesToDecode );
+
+        assertThat( output.flip().toString() )
+            .isEqualTo( PATTERN2 );
+    }
+
+    @Test
+    public void shouldDecodeShifted() throws Exception
+    {
+        CharsetDecoder decoder = UTF_8.newDecoder().onMalformedInput( REPLACE ).onUnmappableCharacter( REPLACE );
+        ByteBuffer input = ByteBuffer.allocate( 1024 );
+        input.put( PATTERN1.getBytes( UTF_8 ) )
+            .put( 90, (byte) 'A' )
+            .put( 91, (byte) 'B' )
+            .put( 92, (byte) 'C' )
+            .position( 90 );
+        CharBuffer output = CharBuffer.allocate( 1024 );
+        int readBytes =
+            invokeMethod( AbstractStreamDecoder.class, "decodeString", decoder, input, output, 2, true, 0 );
+
+        assertThat( readBytes ).isEqualTo( 2 );
+
+        assertThat( output.flip().toString() )
+            .isEqualTo( "AB" );
+    }
+
+    @Test( expected = IllegalArgumentException.class )
+    public void shouldNotDecode() throws Exception
+    {
+        CharsetDecoder decoder = UTF_8.newDecoder();
+        ByteBuffer input = ByteBuffer.allocate( 100 );
+        int bytesToDecode = 101;
+        CharBuffer output = CharBuffer.allocate( 1000 );
+        invokeMethod( AbstractStreamDecoder.class, "decodeString", decoder, input, output, bytesToDecode, true, 0 );
+    }
+
+    @Test
+    public void shouldReadInt() throws Exception
+    {
+        Channel channel = new Channel( new byte[] {0x01, 0x02, 0x03, 0x04, ':'}, 1 );
+
+        Mock thread = new Mock( channel, new MockForkNodeArguments(),
+            Collections.<Segment, ForkedProcessEventType>emptyMap() );
+
+        Memento memento = thread.new Memento();
+
+        assertThat( thread.readInt( memento ) )
+            .isEqualTo( new BigInteger( new byte[] {0x01, 0x02, 0x03, 0x04} ).intValue() );
+    }
+
+    @Test
+    public void shouldReadInteger() throws Exception
+    {
+        Channel channel = new Channel( new byte[] {(byte) 0xff, 0x01, 0x02, 0x03, 0x04, ':'}, 1 );
+
+        Mock thread = new Mock( channel, new MockForkNodeArguments(),
+            Collections.<Segment, ForkedProcessEventType>emptyMap() );
+
+        Memento memento = thread.new Memento();
+        assertThat( thread.readInteger( memento ) )
+            .isEqualTo( new BigInteger( new byte[] {0x01, 0x02, 0x03, 0x04} ).intValue() );
+    }
+
+    @Test
+    public void shouldReadNullInteger() throws Exception
+    {
+        Channel channel = new Channel( new byte[] {(byte) 0x00, ':'}, 1 );
+
+        Mock thread = new Mock( channel, new MockForkNodeArguments(),
+            Collections.<Segment, ForkedProcessEventType>emptyMap() );
+
+        Memento memento = thread.new Memento();
+        assertThat( thread.readInteger( memento ) )
+            .isNull();
+    }
+
+    @Test( expected = EOFException.class )
+    public void shouldNotReadString() throws Exception
+    {
+        Channel channel = new Channel( PATTERN1.getBytes(), PATTERN1.length() );
+        channel.read( ByteBuffer.allocate( 100 ) );
+
+        Mock thread = new Mock( channel, new MockForkNodeArguments(),
+            Collections.<Segment, ForkedProcessEventType>emptyMap() );
+
+        Memento memento = thread.new Memento();
+        invokeMethod( thread, "readString", memento, 10 );
+    }
+
+    @Test
+    public void shouldReadString() throws Exception
+    {
+        Channel channel = new Channel( PATTERN1.getBytes(), PATTERN1.length() );
+
+        Mock thread = new Mock( channel, new MockForkNodeArguments(),
+            Collections.<Segment, ForkedProcessEventType>emptyMap() );
+
+        Memento memento = thread.new Memento();
+        String s = invokeMethod( thread, "readString", memento, 10 );
+        assertThat( s )
+            .isEqualTo( "0123456789" );
+    }
+
+    @Test
+    public void shouldReadStringShiftedBuffer() throws Exception
+    {
+        StringBuilder s = new StringBuilder( 1100 );
+        for ( int i = 0; i < 11; i++ )
+        {
+            s.append( PATTERN1 );
+        }
+
+        Channel channel = new Channel( s.toString().getBytes( UTF_8 ), s.length() );
+
+        Mock thread = new Mock( channel, new MockForkNodeArguments(),
+            Collections.<Segment, ForkedProcessEventType>emptyMap() );
+
+        Memento memento = thread.new Memento();
+        // whatever position will be compacted to 0
+        memento.getByteBuffer().limit( 974 ).position( 974 );
+        assertThat( invokeMethod( thread, "readString", memento, PATTERN1.length() + 3 ) )
+            .isEqualTo( PATTERN1 + "012" );
+    }
+
+    @Test
+    public void shouldReadStringShiftedInput() throws Exception
+    {
+        StringBuilder s = new StringBuilder( 1100 );
+        for ( int i = 0; i < 11; i++ )
+        {
+            s.append( PATTERN1 );
+        }
+
+        Channel channel = new Channel( s.toString().getBytes( UTF_8 ), s.length() );
+        channel.read( ByteBuffer.allocate( 997 ) );
+
+        Mock thread = new Mock( channel, new MockForkNodeArguments(),
+            Collections.<Segment, ForkedProcessEventType>emptyMap() );
+
+        Memento memento = thread.new Memento();
+        assertThat( invokeMethod( thread, "readString", memento, PATTERN1.length() ) )
+            .isEqualTo( "789" + PATTERN1.substring( 0, 97 ) );
+    }
+
+    @Test
+    public void shouldReadMultipleStringsAndShiftedInput() throws Exception
+    {
+        StringBuilder s = new StringBuilder( 5000 );
+
+        for ( int i = 0; i < 50; i++ )
+        {
+            s.append( PATTERN1 );
+        }
+
+        Channel channel = new Channel( s.toString().getBytes( UTF_8 ), s.length() );
+        channel.read( ByteBuffer.allocate( 1997 ) );
+
+        Mock thread = new Mock( channel, new MockForkNodeArguments(),
+            Collections.<Segment, ForkedProcessEventType>emptyMap() );
+
+        Memento memento = thread.new Memento();
+        // whatever position will be compacted to 0
+        memento.getByteBuffer().limit( 974 ).position( 974 );
+
+        StringBuilder expected = new StringBuilder( "789" );
+        for ( int i = 0; i < 11; i++ )
+        {
+            expected.append( PATTERN1 );
+        }
+        expected.setLength( 1100 );
+        assertThat( invokeMethod( thread, "readString", memento, 1100 ) )
+            .isEqualTo( expected.toString() );
+    }
+
+    @Test
+    public void shouldDecode3BytesEncodedSymbol() throws Exception
+    {
+        byte[] encodedSymbol = new byte[] {(byte) -30, (byte) -126, (byte) -84};
+        int countSymbols = 1024;
+        byte[] input = new byte[encodedSymbol.length * countSymbols];
+        for ( int i = 0; i < countSymbols; i++ )
+        {
+            arraycopy( encodedSymbol, 0, input, encodedSymbol.length * i, encodedSymbol.length );
+        }
+
+        Channel channel = new Channel( input, 64 * 1024 );
+        Mock thread = new Mock( channel, new MockForkNodeArguments(),
+            Collections.<Segment, ForkedProcessEventType>emptyMap() );
+        Memento memento = thread.new Memento();
+        String decodedOutput = invokeMethod( thread, "readString", memento, input.length );
+
+        assertThat( decodedOutput )
+            .isEqualTo( new String( input, 0, input.length, UTF_8 ) );
+    }
+
+    @Test
+    public void shouldDecode100Bytes() throws Exception
+    {
+        CharsetDecoder decoder = DEFAULT_STREAM_ENCODING.newDecoder()
+            .onMalformedInput( REPLACE )
+            .onUnmappableCharacter( REPLACE );
+        // empty stream: CharsetDecoder + ByteBuffer.allocate( 0 ) makes 11.5 nanos
+        // empty stream: CharsetDecoder + ByteBuffer.allocate( 0 ) + toString() makes 16.1 nanos
+        ByteBuffer buffer = ByteBuffer.wrap( PATTERN1.getBytes( UTF_8 ) );
+        CharBuffer chars = CharBuffer.allocate( 100 );
+        // uncomment this section for a proper measurement of the exec time
+        TimeUnit.SECONDS.sleep( 2 );
+        System.gc();
+        TimeUnit.SECONDS.sleep( 5 );
+        String s = null;
+        long l1 = System.currentTimeMillis();
+        for ( int i = 0; i < 10_000_000; i++ )
+        {
+            decoder.reset()
+                .decode( buffer, chars, true ); // CharsetDecoder 71 nanos
+            s = chars.flip().toString(); // CharsetDecoder + toString = 91 nanos
+            buffer.clear();
+            chars.clear();
+        }
+        long l2 = System.currentTimeMillis();
+        System.out.println( "decoded 100 bytes within " + ( l2 - l1 ) + " millis (10 million cycles)" );
+        assertThat( s )
+            .isEqualTo( PATTERN1 );
+    }
+
+    @Test
+    public void shouldReadEventType() throws Exception
+    {
+        byte[] array = BOOTERCODE_STDOUT.getOpcodeBinary();
+        Map<Segment, ForkedProcessEventType> messageType =
+            singletonMap( new Segment( array, 0, array.length ), BOOTERCODE_STDOUT );
+
+        byte[] stream = ":maven-surefire-event:\u000E:std-out-stream:".getBytes( UTF_8 );
+        Channel channel = new Channel( stream, 1 );
+        Mock thread = new Mock( channel, new MockForkNodeArguments(), messageType );
+
+        Memento memento = thread.new Memento();
+        memento.setCharset( UTF_8 );
+
+        ForkedProcessEventType eventType = thread.readMessageType( memento );
+        assertThat( eventType )
+            .isEqualTo( BOOTERCODE_STDOUT );
+    }
+
+    @Test( expected = EOFException.class )
+    public void shouldEventTypeReachedEndOfStream() throws Exception
+    {
+        byte[] stream = ":maven-surefire-event:\u000E:xxx".getBytes( UTF_8 );
+        Channel channel = new Channel( stream, 1 );
+        Mock thread = new Mock( channel, new MockForkNodeArguments(), EVENTS );
+
+        Memento memento = thread.new Memento();
+        memento.setCharset( UTF_8 );
+        thread.readMessageType( memento );
+    }
+
+    @Test( expected = MalformedFrameException.class )
+    public void shouldEventTypeReachedMalformedHeader() throws Exception
+    {
+        byte[] stream = ":xxxxx-xxxxxxxx-xxxxx:\u000E:xxx".getBytes( UTF_8 );
+        Channel channel = new Channel( stream, 1 );
+        Mock thread = new Mock( channel, new MockForkNodeArguments(),
+            Collections.<Segment, ForkedProcessEventType>emptyMap() );
+
+        Memento memento = thread.new Memento();
+        memento.setCharset( UTF_8 );
+        thread.readMessageType( memento );
+    }
+
+    @Test
+    public void shouldReadEmptyString() throws Exception
+    {
+        byte[] stream = "\u0000\u0000\u0000\u0000::".getBytes( UTF_8 );
+        Channel channel = new Channel( stream, 1 );
+        Mock thread = new Mock( channel, new MockForkNodeArguments(),
+            Collections.<Segment, ForkedProcessEventType>emptyMap() );
+
+        Memento memento = thread.new Memento();
+        memento.setCharset( UTF_8 );
+
+        assertThat( thread.readString( memento ) )
+            .isEmpty();
+    }
+
+    @Test
+    public void shouldReadNullString() throws Exception
+    {
+        byte[] stream = "\u0000\u0000\u0000\u0001:\u0000:".getBytes( UTF_8 );
+        Channel channel = new Channel( stream, 1 );
+        Mock thread = new Mock( channel, new MockForkNodeArguments(),
+            Collections.<Segment, ForkedProcessEventType>emptyMap() );
+
+        Memento memento = thread.new Memento();
+        memento.setCharset( UTF_8 );
+
+        assertThat( thread.readString( memento ) )
+            .isNull();
+    }
+
+    @Test
+    public void shouldReadSingleCharString() throws Exception
+    {
+        byte[] stream = "\u0000\u0000\u0000\u0001:A:".getBytes( UTF_8 );
+        Channel channel = new Channel( stream, 1 );
+        Mock thread = new Mock( channel, new MockForkNodeArguments(),
+            Collections.<Segment, ForkedProcessEventType>emptyMap() );
+
+        Memento memento = thread.new Memento();
+        memento.setCharset( UTF_8 );
+
+        assertThat( thread.readString( memento ) )
+            .isEqualTo( "A" );
+    }
+
+    @Test
+    public void shouldReadThreeCharactersString() throws Exception
+    {
+        byte[] stream = "\u0000\u0000\u0000\u0003:ABC:".getBytes( UTF_8 );
+        Channel channel = new Channel( stream, 1 );
+        Mock thread = new Mock( channel, new MockForkNodeArguments(),
+            Collections.<Segment, ForkedProcessEventType>emptyMap() );
+
+        Memento memento = thread.new Memento();
+        memento.setCharset( UTF_8 );
+
+        assertThat( thread.readString( memento ) )
+            .isEqualTo( "ABC" );
+    }
+
+    @Test
+    public void shouldReadDefaultCharset() throws Exception
+    {
+        byte[] stream = "\u0005:UTF-8:".getBytes( US_ASCII );
+        Channel channel = new Channel( stream, 1 );
+        Mock thread = new Mock( channel, new MockForkNodeArguments(),
+            Collections.<Segment, ForkedProcessEventType>emptyMap() );
+
+        Memento memento = thread.new Memento();
+        memento.setCharset( UTF_8 );
+
+        assertThat( thread.readCharset( memento ) )
+            .isNotNull()
+            .isEqualTo( UTF_8 );
+    }
+
+    @Test
+    public void shouldReadNonDefaultCharset() throws Exception
+    {
+        byte[] stream = ( (char) 10 + ":ISO_8859_1:" ).getBytes( US_ASCII );
+        Channel channel = new Channel( stream, 1 );
+        Mock thread = new Mock( channel, new MockForkNodeArguments(),
+            Collections.<Segment, ForkedProcessEventType>emptyMap() );
+
+        Memento memento = thread.new Memento();
+        memento.setCharset( UTF_8 );
+
+        assertThat( thread.readCharset( memento ) )
+            .isNotNull()
+            .isEqualTo( ISO_8859_1 );
+    }
+
+    @Test
+    public void shouldSetNonDefaultCharset()
+    {
+        byte[] stream = {};
+        Channel channel = new Channel( stream, 1 );
+        Mock thread = new Mock( channel, new MockForkNodeArguments(),
+            Collections.<Segment, ForkedProcessEventType>emptyMap() );
+        Memento memento = thread.new Memento();
+
+        memento.setCharset( ISO_8859_1 );
+        assertThat( memento.getDecoder().charset() ).isEqualTo( ISO_8859_1 );
+
+        memento.setCharset( UTF_8 );
+        assertThat( memento.getDecoder().charset() ).isEqualTo( UTF_8 );
+
+        memento.reset();
+        assertThat( memento.getDecoder() ).isNotNull();
+        assertThat( memento.getDecoder().charset() ).isEqualTo( UTF_8 );
+    }
+
+    @Test( expected = MalformedFrameException.class )
+    public void malformedCharset() throws Exception
+    {
+        byte[] stream = ( (char) 8 + ":ISO_8859:" ).getBytes( US_ASCII );
+        Channel channel = new Channel( stream, 1 );
+        Mock thread = new Mock( channel, new MockForkNodeArguments(),
+            Collections.<Segment, ForkedProcessEventType>emptyMap() );
+
+        Memento memento = thread.new Memento();
+        memento.setCharset( UTF_8 );
+
+        thread.readCharset( memento );
+    }
+
+    private static class Channel implements ReadableByteChannel
+    {
+        private final byte[] bytes;
+        private final int chunkSize;
+        protected int i;
+
+        Channel( byte[] bytes, int chunkSize )
+        {
+            this.bytes = bytes;
+            this.chunkSize = chunkSize;
+        }
+
+        @Override
+        public int read( ByteBuffer dst )
+        {
+            if ( i == bytes.length )
+            {
+                return -1;
+            }
+            else if ( dst.hasRemaining() )
+            {
+                int length = min( min( chunkSize, bytes.length - i ), dst.remaining() ) ;
+                dst.put( bytes, i, length );
+                i += length;
+                return length;
+            }
+            else
+            {
+                return 0;
+            }
+        }
+
+        @Override
+        public boolean isOpen()
+        {
+            return false;
+        }
+
+        @Override
+        public void close()
+        {
+        }
+    }
+
+    private static class MockForkNodeArguments implements ForkNodeArguments
+    {
+        @Nonnull
+        @Override
+        public String getSessionId()
+        {
+            return null;
+        }
+
+        @Override
+        public int getForkChannelId()
+        {
+            return 0;
+        }
+
+        @Nonnull
+        @Override
+        public File dumpStreamText( @Nonnull String text )
+        {
+            return null;
+        }
+
+        @Nonnull
+        @Override
+        public File dumpStreamException( @Nonnull Throwable t )
+        {
+            return null;
+        }
+
+        @Override
+        public void logWarningAtEnd( @Nonnull String text )
+        {
+        }
+
+        @Nonnull
+        @Override
+        public ConsoleLogger getConsoleLogger()
+        {
+            return null;
+        }
+
+        @Override
+        public File getEventStreamBinaryFile()
+        {
+            return null;
+        }
+
+        @Override
+        public File getCommandStreamBinaryFile()
+        {
+            return null;
+        }
+    }
+
+    private static class Mock extends AbstractStreamDecoder<Event, ForkedProcessEventType, SegmentType>
+    {
+        protected Mock( @Nonnull ReadableByteChannel channel, @Nonnull ForkNodeArguments arguments,
+                        @Nonnull Map<Segment, ForkedProcessEventType> messageTypes )
+        {
+            super( channel, arguments, messageTypes );
+        }
+
+        @Override
+        public Event decode( @Nonnull Memento memento ) throws MalformedChannelException
+        {
+            throw new MalformedChannelException();
+        }
+
+        @Nonnull
+        @Override
+        protected byte[] getEncodedMagicNumber()
+        {
+            return Constants.MAGIC_NUMBER_FOR_EVENTS_BYTES;
+        }
+
+        @Nonnull
+        @Override
+        protected SegmentType[] nextSegmentType( @Nonnull ForkedProcessEventType messageType )
+        {
+            return new SegmentType[] {END_OF_FRAME};
+        }
+
+        @Nonnull
+        @Override
+        protected Event toMessage(
+            @Nonnull ForkedProcessEventType messageType, RunMode runMode,
+            @Nonnull Memento memento ) throws MalformedFrameException
+        {
+            return null;
+        }
+
+        @Override
+        public void close() throws Exception
+        {
+        }
+    }
+}
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/api/stream/AbstractStreamEncoderTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/api/stream/AbstractStreamEncoderTest.java
new file mode 100644
index 0000000..e36373c
--- /dev/null
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/api/stream/AbstractStreamEncoderTest.java
@@ -0,0 +1,331 @@
+package org.apache.maven.surefire.api.stream;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.api.booter.ForkedProcessEventType;
+import org.apache.maven.surefire.api.util.internal.WritableBufferedByteChannel;
+import org.junit.Test;
+
+import javax.annotation.Nonnull;
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+
+import static java.nio.charset.StandardCharsets.US_ASCII;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.maven.surefire.api.booter.Constants.MAGIC_NUMBER_FOR_EVENTS_BYTES;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_BYE;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_DEBUG;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_ERROR;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_INFO;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_WARNING;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_JVM_EXIT_ERROR;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_NEXT_TEST;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDERR;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDERR_NEW_LINE;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDOUT;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDOUT_NEW_LINE;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STOP_ON_NEXT_TEST;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_SYSPROPS;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TESTSET_COMPLETED;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TESTSET_STARTING;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_ASSUMPTIONFAILURE;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_ERROR;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_FAILED;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_SKIPPED;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_STARTING;
+import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_SUCCEEDED;
+import static org.apache.maven.surefire.api.report.RunMode.NORMAL_RUN;
+import static org.fest.assertions.Assertions.assertThat;
+
+/**
+ *
+ */
+@SuppressWarnings( { "checkstyle:linelength", "checkstyle:magicnumber" } )
+public class AbstractStreamEncoderTest
+{
+    @Test
+    public void shouldComputeStreamPreemptiveLength()
+    {
+        Encoder streamEncoder = new Encoder( new DummyChannel() );
+        CharsetEncoder encoder = streamEncoder.newCharsetEncoder();
+
+        // :maven-surefire-event:8:sys-prop:10:normal-run:5:UTF-8:0001:kkk:0001:vvv:
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_SYSPROPS.getOpcodeBinary().length, NORMAL_RUN,
+            encoder, 0, "k", "v" ) )
+            .isEqualTo( 72 );
+
+        // :maven-surefire-event:16:testset-starting:10:normal-run:5:UTF-8:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:X0001:0001:sss:0001:sss:0001:sss:
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_TESTSET_STARTING.getOpcodeBinary().length,
+            NORMAL_RUN, encoder, 1, "s", "s", "s", "s", "s", "s", "s", "s", "s" ) )
+            .isEqualTo( 149 );
+
+        // :maven-surefire-event:17:testset-completed:10:normal-run:5:UTF-8:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:X0001:0001:sss:0001:sss:0001:sss:
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_TESTSET_COMPLETED.getOpcodeBinary().length,
+            NORMAL_RUN, encoder, 1, "s", "s", "s", "s", "s", "s", "s", "s", "s" ) )
+            .isEqualTo( 150 );
+
+        // :maven-surefire-event:13:test-starting:10:normal-run:5:UTF-8:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:X0001:0001:sss:0001:sss:0001:sss:
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_TEST_STARTING.getOpcodeBinary().length,
+            NORMAL_RUN, encoder, 1, "s", "s", "s", "s", "s", "s", "s", "s", "s" ) )
+            .isEqualTo( 146 );
+
+        // :maven-surefire-event:14:test-succeeded:10:normal-run:5:UTF-8:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:X0001:0001:sss:0001:sss:0001:sss:
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_TEST_SUCCEEDED.getOpcodeBinary().length,
+            NORMAL_RUN, encoder, 1, "s", "s", "s", "s", "s", "s", "s", "s", "s" ) )
+            .isEqualTo( 147 );
+
+        // :maven-surefire-event:11:test-failed:10:normal-run:5:UTF-8:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:X0001:0001:sss:0001:sss:0001:sss:
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_TEST_FAILED.getOpcodeBinary().length,
+            NORMAL_RUN, encoder, 1, "s", "s", "s", "s", "s", "s", "s", "s", "s" ) )
+            .isEqualTo( 144 );
+
+        // :maven-surefire-event:12:test-skipped:10:normal-run:5:UTF-8:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:X0001:0001:sss:0001:sss:0001:sss:
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_TEST_SKIPPED.getOpcodeBinary().length,
+            NORMAL_RUN, encoder, 1, "s", "s", "s", "s", "s", "s", "s", "s", "s" ) )
+            .isEqualTo( 145 );
+
+        // :maven-surefire-event:10:test-error:10:normal-run:5:UTF-8:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:X0001:0001:sss:0001:sss:0001:sss:
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_TEST_ERROR.getOpcodeBinary().length,
+            NORMAL_RUN, encoder, 1, "s", "s", "s", "s", "s", "s", "s", "s", "s" ) )
+            .isEqualTo( 143 );
+
+        // :maven-surefire-event:23:test-assumption-failure:10:normal-run:5:UTF-8:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:X0001:0001:sss:0001:sss:0001:sss:
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_TEST_ASSUMPTIONFAILURE.getOpcodeBinary().length,
+            NORMAL_RUN, encoder, 1, "s", "s", "s", "s", "s", "s", "s", "s", "s" ) )
+            .isEqualTo( 156 );
+
+        // :maven-surefire-event:14:std-out-stream:10:normal-run:5:UTF-8:0001:sss:
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_STDOUT.getOpcodeBinary().length,
+            NORMAL_RUN, encoder, 0, "s" ) )
+            .isEqualTo( 69 );
+
+        // :maven-surefire-event:23:std-out-stream-new-line:10:normal-run:5:UTF-8:0001:sss:
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_STDOUT_NEW_LINE.getOpcodeBinary().length,
+            NORMAL_RUN, encoder, 0, "s" ) )
+            .isEqualTo( 78 );
+
+        // :maven-surefire-event:14:std-err-stream:10:normal-run:5:UTF-8:0001:sss:
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_STDERR.getOpcodeBinary().length,
+            NORMAL_RUN, encoder, 0, "s" ) )
+            .isEqualTo( 69 );
+
+        // :maven-surefire-event:23:std-err-stream-new-line:10:normal-run:5:UTF-8:0001:sss:
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_STDERR_NEW_LINE.getOpcodeBinary().length,
+            NORMAL_RUN, encoder, 0, "s" ) )
+            .isEqualTo( 78 );
+
+        // :maven-surefire-event:16:console-info-log:5:UTF-8:0001:sss:
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_CONSOLE_INFO.getOpcodeBinary().length,
+            null, encoder, 0, "s" ) )
+            .isEqualTo( 58 );
+
+        // :maven-surefire-event:17:console-debug-log:5:UTF-8:0001:sss:
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_CONSOLE_DEBUG.getOpcodeBinary().length,
+            null, encoder, 0, "s" ) )
+            .isEqualTo( 59 );
+
+        // :maven-surefire-event:19:console-warning-log:5:UTF-8:0001:sss:
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_CONSOLE_WARNING.getOpcodeBinary().length,
+            null, encoder, 0, "s" ) )
+            .isEqualTo( 61 );
+
+        // :maven-surefire-event:17:console-error-log:5:UTF-8:0001:sss:
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_CONSOLE_ERROR.getOpcodeBinary().length,
+            null, encoder, 0, "s" ) )
+            .isEqualTo( 59 );
+
+        // :maven-surefire-event:3:bye:
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_BYE.getOpcodeBinary().length,
+            null, null, 0 ) )
+            .isEqualTo( 28 );
+
+        // :maven-surefire-event:17:stop-on-next-test:
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_STOP_ON_NEXT_TEST.getOpcodeBinary().length,
+            null, null, 0 ) )
+            .isEqualTo( 42 );
+
+        // :maven-surefire-event:9:next-test:
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_NEXT_TEST.getOpcodeBinary().length,
+            null, null, 0 ) )
+            .isEqualTo( 34 );
+
+        // :maven-surefire-event:14:jvm-exit-error:
+        assertThat( streamEncoder.estimateBufferLength( BOOTERCODE_JVM_EXIT_ERROR.getOpcodeBinary().length,
+            null, null, 0 ) )
+            .isEqualTo( 39 );
+    }
+
+    @Test
+    public void testSendOpcode()
+    {
+        Encoder streamEncoder = new Encoder( new DummyChannel() );
+        ByteBuffer result = ByteBuffer.allocate( 128 );
+        streamEncoder.encodeHeader( result, BOOTERCODE_TEST_ERROR, NORMAL_RUN );
+        assertThat( toString( result ) )
+            .isEqualTo( ":maven-surefire-event:" + (char) 10 + ":test-error:" + (char) 10 + ":normal-run:" );
+
+        result = ByteBuffer.allocate( 1024 );
+        streamEncoder.encodeHeader( result, BOOTERCODE_CONSOLE_ERROR, null );
+        streamEncoder.encodeCharset( result );
+        assertThat( toString( result ) )
+            .isEqualTo( ":maven-surefire-event:" + (char) 17 + ":console-error-log:" + (char) 5 + ":UTF-8:" );
+    }
+
+    @Test
+    public void testEncodedString()
+    {
+        Encoder streamEncoder = new Encoder( new DummyChannel() );
+        ByteBuffer result = ByteBuffer.allocate( 128 );
+        streamEncoder.encode( streamEncoder.newCharsetEncoder(), result, BOOTERCODE_STDOUT, NORMAL_RUN, null, "msg" );
+        assertThat( toString( result ) )
+            .isEqualTo( ":maven-surefire-event:\u000e:std-out-stream:"
+                + (char) 10 + ":normal-run:\u0005:UTF-8:\u0000\u0000\u0000\u0001:\u0000:\u0000\u0000\u0000\u0003:msg:" );
+    }
+
+    @Test
+    public void testIntegerAsNull()
+    {
+        Encoder streamEncoder = new Encoder( new DummyChannel() );
+        ByteBuffer result = ByteBuffer.allocate( 4 );
+        streamEncoder.encodeInteger( result, null );
+        assertThat( result.position() ).isEqualTo( 2 );
+        assertThat( result.get( 0 ) ).isEqualTo( (byte) 0 );
+        assertThat( result.get( 1 ) ).isEqualTo( (byte) ':' );
+    }
+
+    @Test
+    public void testInteger()
+    {
+        Encoder streamEncoder = new Encoder( new DummyChannel() );
+        ByteBuffer result = ByteBuffer.allocate( 8 );
+        streamEncoder.encodeInteger( result, 5 );
+        assertThat( result.position() ).isEqualTo( 6 );
+        result.position( 0 );
+        assertThat( result.get() ).isEqualTo( (byte) 0xff );
+        assertThat( result.getInt() ).isEqualTo( (byte) 5 );
+        assertThat( result.get( 5 ) ).isEqualTo( (byte) ':' );
+    }
+
+    @Test
+    public void testWrite() throws Exception
+    {
+        DummyChannel channel = new DummyChannel();
+        Encoder streamEncoder = new Encoder( channel );
+        streamEncoder.write( ByteBuffer.allocate( 0 ), false );
+        assertThat( channel.writeBuffered ).isTrue();
+        assertThat( channel.write ).isFalse();
+        channel.writeBuffered = false;
+        channel.write = false;
+        streamEncoder.write( ByteBuffer.allocate( 0 ), true );
+        assertThat( channel.writeBuffered ).isFalse();
+        assertThat( channel.write ).isTrue();
+    }
+
+    private static String toString( ByteBuffer frame )
+    {
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        frame.flip();
+        os.write( frame.array(), frame.arrayOffset() + frame.position(), frame.remaining() );
+        return new String( os.toByteArray(), UTF_8 );
+    }
+
+    private static class Encoder extends AbstractStreamEncoder<ForkedProcessEventType>
+    {
+
+        Encoder( WritableBufferedByteChannel out )
+        {
+            super( out );
+        }
+
+        @Nonnull
+        @Override
+        public byte[] getEncodedMagicNumber()
+        {
+            return MAGIC_NUMBER_FOR_EVENTS_BYTES;
+        }
+
+        @Nonnull
+        @Override
+        protected byte[] enumToByteArray( ForkedProcessEventType forkedProcessEventType )
+        {
+            return forkedProcessEventType.getOpcodeBinary();
+        }
+
+        @Nonnull
+        @Override
+        protected byte[] getEncodedCharsetName()
+        {
+            return getCharset().name().getBytes( US_ASCII );
+        }
+
+        @Nonnull
+        @Override
+        public Charset getCharset()
+        {
+            return UTF_8;
+        }
+
+        @Nonnull
+        @Override
+        public CharsetEncoder newCharsetEncoder()
+        {
+            return getCharset().newEncoder();
+        }
+    }
+
+    private static class DummyChannel implements WritableBufferedByteChannel
+    {
+        boolean writeBuffered;
+        boolean write;
+
+        @Override
+        public void writeBuffered( ByteBuffer src )
+        {
+            writeBuffered = true;
+        }
+
+        @Override
+        public long countBufferOverflows()
+        {
+            return 0;
+        }
+
+        @Override
+        public int write( ByteBuffer src )
+        {
+            write = true;
+            return 0;
+        }
+
+        @Override
+        public boolean isOpen()
+        {
+            return false;
+        }
+
+        @Override
+        public void close()
+        {
+        }
+    }
+  
+}
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterConstants.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterConstants.java
index ae950df..dcf7361 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterConstants.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterConstants.java
@@ -42,7 +42,6 @@ public final class BooterConstants
     public static final String TESTARTIFACT_VERSION = "testFwJarVersion";
     public static final String TESTARTIFACT_CLASSIFIER = "testFwJarClassifier";
     public static final String REQUESTEDTEST = "requestedTest";
-    public static final String REQUESTEDTESTMETHOD = "requestedTestMethod";
     public static final String SOURCE_DIRECTORY = "testSuiteDefinitionTestSourceDirectory";
     public static final String TEST_CLASSES_DIRECTORY = "testClassesDirectory";
     public static final String RUN_ORDER = "runOrder";
@@ -60,4 +59,5 @@ public final class BooterConstants
     public static final String PLUGIN_PID = "pluginPid";
     public static final String PROCESS_CHECKER = "processChecker";
     public static final String FORK_NODE_CONNECTION_STRING = "forkNodeConnectionString";
+    public static final String FORK_NUMBER = "forkNumber";
 }
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterDeserializer.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterDeserializer.java
index 6a6cfae..ca5328e 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterDeserializer.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterDeserializer.java
@@ -62,6 +62,11 @@ public class BooterDeserializer
         properties = SystemPropertyManager.loadProperties( inputStream );
     }
 
+    public int getForkNumber()
+    {
+        return properties.getIntProperty( FORK_NUMBER );
+    }
+
     /**
      * Describes the current connection channel used by the client in the forked JVM
      * in order to connect to the plugin process.
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/CommandReader.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/CommandReader.java
index ebe72a9..6599433 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/CommandReader.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/CommandReader.java
@@ -336,11 +336,11 @@ public final class CommandReader implements CommandChainReader
         {
             CommandReader.this.startMonitor.countDown();
             boolean isTestSetFinished = false;
-            try
+            try ( MasterProcessChannelDecoder commandReader = CommandReader.this.decoder )
             {
                 while ( CommandReader.this.state.get() == RUNNABLE )
                 {
-                    Command command = CommandReader.this.decoder.decode();
+                    Command command = commandReader.decode();
                     switch ( command.getCommandType() )
                     {
                         case RUN_CLASS:
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
index d8e3bfe..f00cb6a 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
@@ -27,16 +27,17 @@ import org.apache.maven.surefire.api.booter.ForkingReporterFactory;
 import org.apache.maven.surefire.api.booter.MasterProcessChannelDecoder;
 import org.apache.maven.surefire.api.booter.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.api.booter.Shutdown;
-import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelProcessorFactory;
-import org.apache.maven.surefire.booter.spi.SurefireMasterProcessChannelProcessorFactory;
+import org.apache.maven.surefire.api.fork.ForkNodeArguments;
 import org.apache.maven.surefire.api.provider.CommandListener;
 import org.apache.maven.surefire.api.provider.ProviderParameters;
 import org.apache.maven.surefire.api.provider.SurefireProvider;
 import org.apache.maven.surefire.api.report.LegacyPojoStackTraceWriter;
 import org.apache.maven.surefire.api.report.StackTraceWriter;
+import org.apache.maven.surefire.api.testset.TestSetFailedException;
+import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelProcessorFactory;
+import org.apache.maven.surefire.booter.spi.SurefireMasterProcessChannelProcessorFactory;
 import org.apache.maven.surefire.shared.utils.cli.ShutdownHookUtils;
 import org.apache.maven.surefire.spi.MasterProcessChannelProcessorFactory;
-import org.apache.maven.surefire.api.testset.TestSetFailedException;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -61,13 +62,14 @@ import static java.lang.Thread.currentThread;
 import static java.util.ServiceLoader.load;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.apache.maven.surefire.api.cli.CommandLineOption.LOGGING_LEVEL_DEBUG;
+import static org.apache.maven.surefire.api.util.ReflectionUtils.instantiateOneArg;
+import static org.apache.maven.surefire.api.util.internal.DaemonThreadFactory.newDaemonThreadFactory;
+import static org.apache.maven.surefire.api.util.internal.StringUtils.NL;
 import static org.apache.maven.surefire.booter.ProcessCheckerType.ALL;
 import static org.apache.maven.surefire.booter.ProcessCheckerType.NATIVE;
 import static org.apache.maven.surefire.booter.ProcessCheckerType.PING;
 import static org.apache.maven.surefire.booter.SystemPropertyManager.setSystemProperties;
-import static org.apache.maven.surefire.api.util.ReflectionUtils.instantiateOneArg;
-import static org.apache.maven.surefire.api.util.internal.DaemonThreadFactory.newDaemonThreadFactory;
-import static org.apache.maven.surefire.api.util.internal.StringUtils.NL;
 
 /**
  * The part of the booter that is unique to a forked vm.
@@ -113,10 +115,13 @@ public final class ForkedBooter
         DumpErrorSingleton.getSingleton()
                 .init( providerConfiguration.getReporterConfiguration().getReportsDirectory(), dumpFileName );
 
+        int forkNumber = booterDeserializer.getForkNumber();
+
         if ( isDebugging() )
         {
             DumpErrorSingleton.getSingleton()
-                    .dumpText( "Found Maven process ID " + booterDeserializer.getPluginPid() );
+                    .dumpText( "Found Maven process ID " + booterDeserializer.getPluginPid()
+                        + " for the fork " + forkNumber + "." );
         }
 
         startupConfiguration = booterDeserializer.getStartupConfiguration();
@@ -124,8 +129,11 @@ public final class ForkedBooter
         String channelConfig = booterDeserializer.getConnectionString();
         channelProcessorFactory = lookupDecoderFactory( channelConfig );
         channelProcessorFactory.connect( channelConfig );
-        eventChannel = channelProcessorFactory.createEncoder();
-        MasterProcessChannelDecoder decoder = channelProcessorFactory.createDecoder();
+        boolean isDebugging = isDebugging();
+        boolean debug = isDebugging || providerConfiguration.getMainCliOptions().contains( LOGGING_LEVEL_DEBUG );
+        ForkNodeArguments args = new ForkedNodeArg( forkNumber, debug );
+        eventChannel = channelProcessorFactory.createEncoder( args );
+        MasterProcessChannelDecoder decoder = channelProcessorFactory.createDecoder( args );
 
         flushEventChannelOnExit();
 
@@ -133,7 +141,7 @@ public final class ForkedBooter
         ConsoleLogger logger = (ConsoleLogger) forkingReporterFactory.createReporter();
         commandReader = new CommandReader( decoder, providerConfiguration.getShutdown(), logger );
 
-        pingScheduler = isDebugging() ? null : listenToShutdownCommands( booterDeserializer.getPluginPid(), logger );
+        pingScheduler = isDebugging ? null : listenToShutdownCommands( booterDeserializer.getPluginPid(), logger );
 
         systemExitTimeoutInSeconds = providerConfiguration.systemExitTimeout( DEFAULT_SYSTEM_EXIT_TIMEOUT_IN_SECONDS );
 
@@ -671,4 +679,5 @@ public final class ForkedBooter
         return ManagementFactory.getRuntimeMXBean()
                 .getName();
     }
+
 }
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedNodeArg.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedNodeArg.java
new file mode 100644
index 0000000..4bed5ca
--- /dev/null
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedNodeArg.java
@@ -0,0 +1,97 @@
+package org.apache.maven.surefire.booter;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.plugin.surefire.log.api.NullConsoleLogger;
+import org.apache.maven.surefire.api.booter.DumpErrorSingleton;
+import org.apache.maven.surefire.api.fork.ForkNodeArguments;
+
+import javax.annotation.Nonnull;
+import java.io.File;
+
+/**
+ *
+ */
+public final class ForkedNodeArg implements ForkNodeArguments
+{
+    private final int forkChannelId;
+    private final ConsoleLogger logger;
+    private final boolean isDebug;
+
+    public ForkedNodeArg( int forkChannelId, boolean isDebug )
+    {
+        this.forkChannelId = forkChannelId;
+        logger = new NullConsoleLogger();
+        this.isDebug = isDebug;
+    }
+
+    @Nonnull
+    @Override
+    public String getSessionId()
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int getForkChannelId()
+    {
+        return forkChannelId;
+    }
+
+    @Override
+    @Nonnull
+    public File dumpStreamText( @Nonnull String text )
+    {
+        return DumpErrorSingleton.getSingleton().dumpStreamText( text );
+    }
+
+    @Nonnull
+    @Override
+    public File dumpStreamException( @Nonnull Throwable t )
+    {
+        return DumpErrorSingleton.getSingleton().dumpStreamException( t, t.getLocalizedMessage() );
+    }
+
+    @Override
+    public void logWarningAtEnd( @Nonnull String text )
+    {
+        // do nothing - the log message of forked VM already goes to the dump file
+    }
+
+    @Nonnull
+    @Override
+    public ConsoleLogger getConsoleLogger()
+    {
+        return logger;
+    }
+
+    @Override
+    public File getEventStreamBinaryFile()
+    {
+        return null;
+    }
+
+    @Override
+    public File getCommandStreamBinaryFile()
+    {
+        return isDebug ? DumpErrorSingleton.getSingleton().getCommandStreamBinaryFile() : null;
+    }
+}
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PropertiesWrapper.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PropertiesWrapper.java
index c0004e5..b5cccf4 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PropertiesWrapper.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PropertiesWrapper.java
@@ -62,7 +62,7 @@ public class PropertiesWrapper
 
     public boolean getBooleanProperty( String propertyName )
     {
-        return Boolean.valueOf( properties.get( propertyName ) );
+        return Boolean.parseBoolean( properties.get( propertyName ) );
     }
 
     public int getIntProperty( String propertyName )
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/CommandChannelDecoder.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/CommandChannelDecoder.java
new file mode 100644
index 0000000..85bda73
--- /dev/null
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/CommandChannelDecoder.java
@@ -0,0 +1,86 @@
+package org.apache.maven.surefire.booter.spi;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.api.booter.Command;
+import org.apache.maven.surefire.api.booter.MasterProcessChannelDecoder;
+import org.apache.maven.surefire.api.fork.ForkNodeArguments;
+import org.apache.maven.surefire.api.stream.AbstractStreamDecoder.Memento;
+import org.apache.maven.surefire.api.stream.MalformedChannelException;
+import org.apache.maven.surefire.booter.stream.CommandDecoder;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.nio.channels.ReadableByteChannel;
+
+/**
+ * magic number : opcode [: opcode specific data]*
+ * <br>
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 3.0.0-M5
+ */
+public class CommandChannelDecoder implements MasterProcessChannelDecoder
+{
+    private final CommandDecoder decoder;
+    private Memento memento;
+
+    public CommandChannelDecoder( @Nonnull ReadableByteChannel channel,
+                                  @Nonnull ForkNodeArguments arguments )
+    {
+        decoder = new CommandDecoder( channel, arguments );
+    }
+
+    @Override
+    @Nonnull
+    @SuppressWarnings( "checkstyle:innerassignment" )
+    public Command decode() throws IOException
+    {
+        if ( memento == null )
+        {
+            // do not create memento in constructor because the constructor is called in another thread
+            // memento is the thread confinement object
+            memento = decoder.new Memento();
+        }
+
+        do
+        {
+            try
+            {
+                Command command = decoder.decode( memento );
+                if ( command != null )
+                {
+                    return command;
+                }
+            }
+            catch ( MalformedChannelException e )
+            {
+                // a bad stream, already logged the stream down to a dump file or console, and continue till OEF
+            }
+        }
+        while ( true );
+    }
+
+    @Override
+    public void close() throws IOException
+    {
+        decoder.close();
+    }
+}
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/EventChannelEncoder.java
similarity index 61%
rename from surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java
rename to surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/EventChannelEncoder.java
index 0d8f787..8f78072 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/EventChannelEncoder.java
@@ -28,6 +28,7 @@ import org.apache.maven.surefire.api.report.RunMode;
 import org.apache.maven.surefire.api.report.SafeThrowable;
 import org.apache.maven.surefire.api.report.StackTraceWriter;
 import org.apache.maven.surefire.api.util.internal.WritableBufferedByteChannel;
+import org.apache.maven.surefire.booter.stream.EventEncoder;
 
 import javax.annotation.Nonnull;
 import java.io.IOException;
@@ -39,12 +40,7 @@ import java.util.Map;
 import java.util.Map.Entry;
 import java.util.concurrent.atomic.AtomicBoolean;
 
-import static java.lang.Math.ceil;
-import static java.nio.CharBuffer.wrap;
 import static java.util.Objects.requireNonNull;
-import static org.apache.maven.surefire.api.booter.Constants.DEFAULT_STREAM_ENCODING;
-import static org.apache.maven.surefire.api.booter.Constants.DEFAULT_STREAM_ENCODING_BYTES;
-import static org.apache.maven.surefire.api.booter.Constants.MAGIC_NUMBER_BYTES;
 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_BYE;
 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_DEBUG;
 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_ERROR;
@@ -67,7 +63,6 @@ import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTER
 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_STARTING;
 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_SUCCEEDED;
 import static org.apache.maven.surefire.api.report.RunMode.NORMAL_RUN;
-import static org.apache.maven.surefire.api.report.RunMode.RERUN_TEST_AFTER_FAILURE;
 
 /**
  * magic number : opcode : run mode [: opcode specific data]*
@@ -77,39 +72,45 @@ import static org.apache.maven.surefire.api.report.RunMode.RERUN_TEST_AFTER_FAIL
  * @since 3.0.0-M4
  */
 @SuppressWarnings( "checkstyle:linelength" )
-public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEncoder
+public class EventChannelEncoder extends EventEncoder implements MasterProcessChannelEncoder
 {
-    private static final byte[] INT_BINARY = new byte[] {0, 0, 0, 0};
-    private static final byte BOOLEAN_NON_NULL_OBJECT = (byte) 0xff;
-    private static final byte BOOLEAN_NULL_OBJECT = (byte) 0;
-
-    private final WritableBufferedByteChannel out;
     private final RunMode runMode;
     private final AtomicBoolean trouble = new AtomicBoolean();
     private volatile boolean onExit;
 
-    public LegacyMasterProcessChannelEncoder( @Nonnull WritableBufferedByteChannel out )
+    /**
+     * The encoder for events and normal test mode.
+     *
+     * @param out     the channel available for writing the events
+     */
+    public EventChannelEncoder( @Nonnull WritableBufferedByteChannel out )
     {
         this( out, NORMAL_RUN );
     }
 
-    protected LegacyMasterProcessChannelEncoder( @Nonnull WritableBufferedByteChannel out, @Nonnull RunMode runMode )
+    /**
+     * The encoder for events and any test mode.
+     *
+     * @param out     the channel available for writing the events
+     * @param runMode run mode
+     */
+    public EventChannelEncoder( @Nonnull WritableBufferedByteChannel out, @Nonnull RunMode runMode )
     {
-        this.out = requireNonNull( out );
+        super( out );
         this.runMode = requireNonNull( runMode );
     }
 
-    @Override
+    /*@Override
     public MasterProcessChannelEncoder asRerunMode() // todo apply this and rework providers
     {
-        return new LegacyMasterProcessChannelEncoder( out, RERUN_TEST_AFTER_FAILURE );
+        return new EventChannelEncoder( streamEncoder, RERUN_TEST_AFTER_FAILURE );
     }
 
     @Override
     public MasterProcessChannelEncoder asNormalMode()
     {
-        return new LegacyMasterProcessChannelEncoder( out, NORMAL_RUN );
-    }
+        return new EventChannelEncoder( streamEncoder, NORMAL_RUN );
+    }*/
 
     @Override
     public boolean checkError()
@@ -121,13 +122,13 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
     public void onJvmExit()
     {
         onExit = true;
-        encodeAndPrintEvent( ByteBuffer.wrap( new byte[] {'\n'} ), true );
+        write( ByteBuffer.wrap( new byte[] {'\n'} ), true );
     }
 
     @Override
-    public void sendSystemProperties( Map<String, String> sysProps )
+    public void systemProperties( Map<String, String> sysProps )
     {
-        CharsetEncoder encoder = DEFAULT_STREAM_ENCODING.newEncoder();
+        CharsetEncoder encoder = newCharsetEncoder();
         ByteBuffer result = null;
         for ( Iterator<Entry<String, String>> it = sysProps.entrySet().iterator(); it.hasNext(); )
         {
@@ -135,13 +136,14 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
             String key = entry.getKey();
             String value = entry.getValue();
 
-            int bufferLength = estimateBufferLength( BOOTERCODE_SYSPROPS, runMode, encoder, 0, key, value );
+            int bufferLength =
+                estimateBufferLength( BOOTERCODE_SYSPROPS.getOpcode().length(), runMode, encoder, 0, key, value );
             result = result != null && result.capacity() >= bufferLength ? result : ByteBuffer.allocate( bufferLength );
             result.clear();
-            // :maven-surefire-event:sys-prop:rerun-test-after-failure:UTF-8:0000000000:<key>:0000000000:<value>:
+            // :maven-surefire-event:sys-prop:rerun-test-after-failure:UTF-8:<integer>:<key>:<integer>:<value>:
             encode( encoder, result, BOOTERCODE_SYSPROPS, runMode, key, value );
-            boolean sendImmediately = !it.hasNext();
-            encodeAndPrintEvent( result, sendImmediately );
+            boolean sync = !it.hasNext();
+            write( result, sync );
         }
     }
 
@@ -210,14 +212,14 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
     private void setOutErr( ForkedProcessEventType eventType, String message )
     {
         ByteBuffer result = encodeMessage( eventType, runMode, message );
-        encodeAndPrintEvent( result, false );
+        write( result, false );
     }
 
     @Override
     public void consoleInfoLog( String message )
     {
         ByteBuffer result = encodeMessage( BOOTERCODE_CONSOLE_INFO, null, message );
-        encodeAndPrintEvent( result, true );
+        write( result, true );
     }
 
     @Override
@@ -235,13 +237,15 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
     @Override
     public void consoleErrorLog( String message, Throwable t )
     {
-        CharsetEncoder encoder = DEFAULT_STREAM_ENCODING.newEncoder();
+        CharsetEncoder encoder = newCharsetEncoder();
         String stackTrace = t == null ? null : ConsoleLoggerUtils.toString( t );
-        int bufferMaxLength = estimateBufferLength( BOOTERCODE_CONSOLE_ERROR, null, encoder, 0, message, stackTrace );
+        int bufferMaxLength = estimateBufferLength( BOOTERCODE_CONSOLE_ERROR.getOpcode().length(), null, encoder, 0,
+            message, stackTrace );
         ByteBuffer result = ByteBuffer.allocate( bufferMaxLength );
-        encodeHeader( encoder, result, BOOTERCODE_CONSOLE_ERROR, null );
+        encodeHeader( result, BOOTERCODE_CONSOLE_ERROR, null );
+        encodeCharset( result );
         encode( encoder, result, message, null, stackTrace );
-        encodeAndPrintEvent( result, true );
+        write( result, true );
     }
 
     @Override
@@ -254,14 +258,14 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
     public void consoleDebugLog( String message )
     {
         ByteBuffer result = encodeMessage( BOOTERCODE_CONSOLE_DEBUG, null, message );
-        encodeAndPrintEvent( result, true );
+        write( result, true );
     }
 
     @Override
     public void consoleWarningLog( String message )
     {
         ByteBuffer result = encodeMessage( BOOTERCODE_CONSOLE_WARNING, null, message );
-        encodeAndPrintEvent( result, true );
+        write( result, true );
     }
 
     @Override
@@ -289,58 +293,51 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
     }
 
     private void error( StackTraceWriter stackTraceWriter, boolean trimStackTraces, ForkedProcessEventType eventType,
-                        @SuppressWarnings( "SameParameterValue" ) boolean sendImmediately )
+                        @SuppressWarnings( "SameParameterValue" ) boolean sync )
     {
-        CharsetEncoder encoder = DEFAULT_STREAM_ENCODING.newEncoder();
+        CharsetEncoder encoder = newCharsetEncoder();
         StackTrace stackTraceWrapper = new StackTrace( stackTraceWriter, trimStackTraces );
-        int bufferMaxLength = estimateBufferLength( eventType, null, encoder, 0,
+        int bufferMaxLength = estimateBufferLength( eventType.getOpcode().length(), null, encoder, 0,
             stackTraceWrapper.message, stackTraceWrapper.smartTrimmedStackTrace, stackTraceWrapper.stackTrace );
         ByteBuffer result = ByteBuffer.allocate( bufferMaxLength );
 
-        encodeHeader( encoder, result, eventType, null );
+        encodeHeader( result, eventType, null );
+        encodeCharset( result );
         encode( encoder, result, stackTraceWrapper );
-        encodeAndPrintEvent( result, sendImmediately );
+        write( result, sync );
     }
 
-    /**
-     * :maven-surefire-event:testset-starting:rerun-test-after-failure:UTF-8:0000000000:SourceName:0000000000:SourceText:0000000000:Name:0000000000:NameText:0000000000:Group:0000000000:Message:0000000000:ElapsedTime:0000000000:LocalizedMessage:0000000000:SmartTrimmedStackTrace:0000000000:toStackTrace( stw, trimStackTraces ):0000000000:
-     *
-     */
+    // example
+    // :maven-surefire-event:testset-starting:rerun-test-after-failure:UTF-8:<integer>:SourceName:<integer>:SourceText:<integer>:Name:<integer>:NameText:<integer>:Group:<integer>:Message:<integer>:ElapsedTime:<integer>:LocalizedMessage:<integer>:SmartTrimmedStackTrace:<integer>:toStackTrace( stw, trimStackTraces ):<integer>:
     private void encode( ForkedProcessEventType operation, RunMode runMode, ReportEntry reportEntry,
-                         boolean trimStackTraces, @SuppressWarnings( "SameParameterValue" ) boolean sendImmediately )
+                         boolean trimStackTraces, @SuppressWarnings( "SameParameterValue" ) boolean sync )
     {
         ByteBuffer result = encode( operation, runMode, reportEntry, trimStackTraces );
-        encodeAndPrintEvent( result, sendImmediately );
+        write( result, sync );
     }
 
-    private void encodeOpcode( ForkedProcessEventType eventType, boolean sendImmediately )
+    private void encodeOpcode( ForkedProcessEventType eventType, boolean sync )
     {
-        int bufferMaxLength = estimateBufferLength( eventType, null, null, 0 );
+        int bufferMaxLength = estimateBufferLength( eventType.getOpcode().length(), null, null, 0 );
         ByteBuffer result = ByteBuffer.allocate( bufferMaxLength );
-        encodeOpcode( result, eventType, null );
-        encodeAndPrintEvent( result, sendImmediately );
+        encodeHeader( result, eventType, null );
+        write( result, sync );
     }
 
-    private void encodeAndPrintEvent( ByteBuffer frame, boolean sendImmediately )
+    @Override
+    protected void write( ByteBuffer frame, boolean sync )
     {
         final boolean wasInterrupted = Thread.interrupted();
         try
         {
-            if ( sendImmediately )
-            {
-                out.write( frame );
-            }
-            else
-            {
-                out.writeBuffered( frame );
-            }
+            super.write( frame, sync );
         }
         catch ( ClosedChannelException e )
         {
             if ( !onExit )
             {
                 String event = new String( frame.array(), frame.arrayOffset() + frame.position(), frame.remaining(),
-                    DEFAULT_STREAM_ENCODING );
+                    getCharset() );
 
                 DumpErrorSingleton.getSingleton()
                     .dumpException( e, "Channel closed while writing the event '" + event + "'." );
@@ -363,23 +360,13 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
         }
     }
 
-    static void encode( CharsetEncoder encoder, ByteBuffer result,
-                        ForkedProcessEventType operation, RunMode runMode, String... messages )
-    {
-        encodeHeader( encoder, result, operation, runMode );
-        for ( String message : messages )
-        {
-            encodeString( encoder, result, message );
-        }
-    }
-
-    static void encode( CharsetEncoder encoder, ByteBuffer result, StackTrace stw )
+    private void encode( CharsetEncoder encoder, ByteBuffer result, StackTrace stw )
     {
         encode( encoder, result, stw.message, stw.smartTrimmedStackTrace, stw.stackTrace );
     }
 
-    private static void encode( CharsetEncoder encoder, ByteBuffer result,
-                                String message, String smartStackTrace, String stackTrace )
+    private void encode( CharsetEncoder encoder, ByteBuffer result,
+                         String message, String smartStackTrace, String stackTrace )
     {
         encodeString( encoder, result, message );
         encodeString( encoder, result, smartStackTrace );
@@ -399,21 +386,22 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
      * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_ASSUMPTIONFAILURE}.</li>
      * </ul>
      */
-    static ByteBuffer encode( ForkedProcessEventType operation, RunMode runMode, ReportEntry reportEntry,
-                              boolean trimStackTraces )
+    ByteBuffer encode( ForkedProcessEventType operation, RunMode runMode, ReportEntry reportEntry,
+                               boolean trimStackTraces )
     {
         StackTrace stackTraceWrapper = new StackTrace( reportEntry.getStackTraceWriter(), trimStackTraces );
 
-        CharsetEncoder encoder = DEFAULT_STREAM_ENCODING.newEncoder();
+        CharsetEncoder encoder = newCharsetEncoder();
 
-        int bufferMaxLength = estimateBufferLength( operation, runMode, encoder, 1, reportEntry.getSourceName(),
-            reportEntry.getSourceText(), reportEntry.getName(), reportEntry.getNameText(), reportEntry.getGroup(),
-            reportEntry.getMessage(), stackTraceWrapper.message, stackTraceWrapper.smartTrimmedStackTrace,
-            stackTraceWrapper.stackTrace );
+        int bufferMaxLength = estimateBufferLength( operation.getOpcode().length(), runMode, encoder, 1,
+            reportEntry.getSourceName(), reportEntry.getSourceText(), reportEntry.getName(), reportEntry.getNameText(),
+            reportEntry.getGroup(), reportEntry.getMessage(), stackTraceWrapper.message,
+            stackTraceWrapper.smartTrimmedStackTrace, stackTraceWrapper.stackTrace );
 
         ByteBuffer result = ByteBuffer.allocate( bufferMaxLength );
 
-        encodeHeader( encoder, result, operation, runMode );
+        encodeHeader( result, operation, runMode );
+        encodeCharset( result );
 
         encodeString( encoder, result, reportEntry.getSourceName() );
         encodeString( encoder, result, reportEntry.getSourceText() );
@@ -428,87 +416,15 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
         return result;
     }
 
-    static ByteBuffer encodeMessage( ForkedProcessEventType eventType, RunMode runMode, String message )
+    ByteBuffer encodeMessage( ForkedProcessEventType eventType, RunMode runMode, String message )
     {
-        CharsetEncoder encoder = DEFAULT_STREAM_ENCODING.newEncoder();
-        int bufferMaxLength = estimateBufferLength( eventType, runMode, encoder, 0, message );
+        CharsetEncoder encoder = newCharsetEncoder();
+        int bufferMaxLength = estimateBufferLength( eventType.getOpcode().length(), runMode, encoder, 0, message );
         ByteBuffer result = ByteBuffer.allocate( bufferMaxLength );
-        encodeHeader( encoder, result, eventType, runMode );
-        encodeString( encoder, result, message );
+        encode( encoder, result, eventType, runMode, message );
         return result;
     }
 
-    private static void encodeString( CharsetEncoder encoder, ByteBuffer result, String string )
-    {
-        String nonNullString = nonNull( string );
-
-        int counterPosition = result.position();
-
-        result.put( INT_BINARY ).put( (byte) ':' );
-
-        int msgStart = result.position();
-        encoder.encode( wrap( nonNullString ), result, true );
-        int msgEnd = result.position();
-        int encodedMsgSize = msgEnd - msgStart;
-        result.putInt( counterPosition, encodedMsgSize );
-
-        result.position( msgEnd );
-
-        result.put( (byte) ':' );
-    }
-
-    private static void encodeInteger( ByteBuffer result, Integer i )
-    {
-        if ( i == null )
-        {
-            result.put( BOOLEAN_NULL_OBJECT );
-        }
-        else
-        {
-            result.put( BOOLEAN_NON_NULL_OBJECT ).putInt( i );
-        }
-        result.put( (byte) ':' );
-    }
-
-    static void encodeHeader( CharsetEncoder encoder, ByteBuffer result, ForkedProcessEventType operation,
-                              RunMode runMode )
-    {
-        encodeOpcode( result, operation, runMode );
-        String charsetName = encoder.charset().name();
-        result.put( (byte) charsetName.length() );
-        result.put( (byte) ':' );
-        result.put( DEFAULT_STREAM_ENCODING_BYTES );
-        result.put( (byte) ':' );
-    }
-
-    /**
-     * Used in {@link #bye()}, {@link #stopOnNextTest()} and {@link #encodeOpcode(ForkedProcessEventType, boolean)}
-     * and private methods extending the buffer.
-     *
-     * @param operation opcode
-     * @param runMode   run mode
-     */
-    static void encodeOpcode( ByteBuffer result, ForkedProcessEventType operation, RunMode runMode )
-    {
-        result.put( (byte) ':' );
-        result.put( MAGIC_NUMBER_BYTES );
-        result.put( (byte) ':' );
-        byte[] opcode = operation.getOpcodeBinary();
-        result.put( (byte) opcode.length );
-        result.put( (byte) ':' );
-        result.put( opcode );
-        result.put( (byte) ':' );
-
-        if ( runMode != null )
-        {
-            byte[] runmode = runMode.getRunmodeBinary();
-            result.put( (byte) runmode.length );
-            result.put( (byte) ':' );
-            result.put( runmode );
-            result.put( (byte) ':' );
-        }
-    }
-
     private static String toStackTrace( StackTraceWriter stw, boolean trimStackTraces )
     {
         if ( stw == null )
@@ -519,46 +435,6 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
         return trimStackTraces ? stw.writeTrimmedTraceToString() : stw.writeTraceToString();
     }
 
-    static String nonNull( String msg )
-    {
-        return msg == null ? "\u0000" : msg;
-    }
-
-    static int estimateBufferLength( ForkedProcessEventType eventType, RunMode runMode, CharsetEncoder encoder,
-                                     int integersCounter, String... strings )
-    {
-        assert !( encoder == null && strings.length != 0 );
-
-        // one delimiter character ':' + <string> + one delimiter character ':' +
-        // one byte + one delimiter character ':' + <string> + one delimiter character ':'
-        int lengthOfMetadata = 1 + MAGIC_NUMBER_BYTES.length + 1 + 1 + 1 + eventType.getOpcode().length() + 1;
-
-        if ( runMode != null )
-        {
-            // one byte of length + one delimiter character ':' + <string> + one delimiter character ':'
-            lengthOfMetadata += 1 + 1 + runMode.geRunmode().length() + 1;
-        }
-
-        if ( encoder != null )
-        {
-            // one byte of length + one delimiter character ':' + <string> + one delimiter character ':'
-            lengthOfMetadata += 1 + 1 + encoder.charset().name().length() + 1;
-        }
-
-        // one byte (0x00 if NULL) + 4 bytes for integer + one delimiter character ':'
-        int lengthOfData = ( 1 + 4 + 1 ) * integersCounter;
-
-        for ( String string : strings )
-        {
-            String s = string == null ? "\u0000" : string;
-            // 4 bytes of string length + one delimiter character ':' + <string> + one delimiter character ':'
-            lengthOfData += 4 + 1 + (int) ceil( encoder.maxBytesPerChar() * s.length() ) + 1;
-        }
-
-
-        return lengthOfMetadata + lengthOfData;
-    }
-
     private static final class StackTrace
     {
         final String message;
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelDecoder.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelDecoder.java
deleted file mode 100644
index 9d29495..0000000
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelDecoder.java
+++ /dev/null
@@ -1,190 +0,0 @@
-package org.apache.maven.surefire.booter.spi;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.maven.surefire.api.booter.Command;
-import org.apache.maven.surefire.api.booter.DumpErrorSingleton;
-import org.apache.maven.surefire.api.booter.MasterProcessCommand;
-import org.apache.maven.surefire.api.booter.MasterProcessChannelDecoder;
-import org.apache.maven.surefire.api.util.internal.ImmutableMap;
-
-import javax.annotation.Nonnull;
-import java.io.EOFException;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.ReadableByteChannel;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import static org.apache.maven.surefire.api.booter.MasterProcessCommand.BYE_ACK;
-import static org.apache.maven.surefire.api.booter.MasterProcessCommand.MAGIC_NUMBER;
-import static org.apache.maven.surefire.api.booter.MasterProcessCommand.NOOP;
-import static org.apache.maven.surefire.api.booter.MasterProcessCommand.RUN_CLASS;
-import static org.apache.maven.surefire.api.booter.MasterProcessCommand.SHUTDOWN;
-import static org.apache.maven.surefire.api.booter.MasterProcessCommand.SKIP_SINCE_NEXT_TEST;
-import static org.apache.maven.surefire.api.booter.MasterProcessCommand.TEST_SET_FINISHED;
-
-/**
- * magic number : opcode [: opcode specific data]*
- * <br>
- *
- * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 3.0.0-M5
- */
-public class LegacyMasterProcessChannelDecoder implements MasterProcessChannelDecoder
-{
-    private static final Map<String, MasterProcessCommand> COMMAND_OPCODES = stringsToOpcodes();
-
-    private final ReadableByteChannel channel;
-
-    public LegacyMasterProcessChannelDecoder( @Nonnull ReadableByteChannel channel )
-    {
-        this.channel = channel;
-    }
-
-    @Override
-    @Nonnull
-    @SuppressWarnings( "checkstyle:innerassignment" )
-    public Command decode() throws IOException
-    {
-        List<String> tokens = new ArrayList<>( 3 );
-        StringBuilder token = new StringBuilder( MAGIC_NUMBER.length() );
-        ByteBuffer buffer = ByteBuffer.allocate( 1 );
-
-        start:
-        do
-        {
-            boolean endOfStream;
-            tokens.clear();
-            token.setLength( 0 );
-            FrameCompletion completion = null;
-            for ( boolean frameStarted = false; !( endOfStream = channel.read( buffer ) == -1 ); completion = null )
-            {
-                buffer.flip();
-                char c = (char) buffer.get();
-                buffer.clear();
-
-                if ( !frameStarted )
-                {
-                    if ( c == ':' )
-                    {
-                        frameStarted = true;
-                        token.setLength( 0 );
-                        tokens.clear();
-                    }
-                }
-                else
-                {
-                    if ( c == ':' )
-                    {
-                        tokens.add( token.toString() );
-                        token.setLength( 0 );
-                        completion = frameCompleteness( tokens );
-                        if ( completion == FrameCompletion.COMPLETE )
-                        {
-                            break;
-                        }
-                        else if ( completion == FrameCompletion.MALFORMED )
-                        {
-                            DumpErrorSingleton.getSingleton()
-                                .dumpStreamText( "Malformed frame with tokens " + tokens );
-                            continue start;
-                        }
-                    }
-                    else
-                    {
-                        token.append( c );
-                    }
-                }
-            }
-
-            if ( completion == FrameCompletion.COMPLETE )
-            {
-                MasterProcessCommand cmd = COMMAND_OPCODES.get( tokens.get( 1 ) );
-                if ( tokens.size() == 2 )
-                {
-                    return new Command( cmd );
-                }
-                else if ( tokens.size() == 3 )
-                {
-                    return new Command( cmd, tokens.get( 2 ) );
-                }
-            }
-
-            if ( endOfStream )
-            {
-                throw new EOFException();
-            }
-        }
-        while ( true );
-    }
-
-    private static FrameCompletion frameCompleteness( List<String> tokens )
-    {
-        if ( !tokens.isEmpty() && !MAGIC_NUMBER.equals( tokens.get( 0 ) ) )
-        {
-            return FrameCompletion.MALFORMED;
-        }
-
-        if ( tokens.size() >= 2 )
-        {
-            String opcode = tokens.get( 1 );
-            MasterProcessCommand cmd = COMMAND_OPCODES.get( opcode );
-            if ( cmd == null )
-            {
-                return FrameCompletion.MALFORMED;
-            }
-            else if ( cmd.hasDataType() == ( tokens.size() == 3 ) )
-            {
-                return FrameCompletion.COMPLETE;
-            }
-        }
-        return FrameCompletion.NOT_COMPLETE;
-    }
-
-    @Override
-    public void close()
-    {
-    }
-
-    /**
-     * Determines whether the frame is complete or malformed.
-     */
-    private enum FrameCompletion
-    {
-        NOT_COMPLETE,
-        COMPLETE,
-        MALFORMED
-    }
-
-    private static Map<String, MasterProcessCommand> stringsToOpcodes()
-    {
-        Map<String, MasterProcessCommand> opcodes = new HashMap<>();
-        opcodes.put( "run-testclass", RUN_CLASS );
-        opcodes.put( "testset-finished", TEST_SET_FINISHED );
-        opcodes.put( "skip-since-next-test", SKIP_SINCE_NEXT_TEST );
-        opcodes.put( "shutdown", SHUTDOWN );
-        opcodes.put( "noop", NOOP );
-        opcodes.put( "bye-ack", BYE_ACK );
-        return new ImmutableMap<>( opcodes );
-    }
-}
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelProcessorFactory.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelProcessorFactory.java
index 6e28764..3d96f06 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelProcessorFactory.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelProcessorFactory.java
@@ -21,8 +21,10 @@ package org.apache.maven.surefire.booter.spi;
 
 import org.apache.maven.surefire.api.booter.MasterProcessChannelDecoder;
 import org.apache.maven.surefire.api.booter.MasterProcessChannelEncoder;
+import org.apache.maven.surefire.api.fork.ForkNodeArguments;
 import org.apache.maven.surefire.api.util.internal.WritableBufferedByteChannel;
 
+import javax.annotation.Nonnull;
 import java.io.IOException;
 import java.net.MalformedURLException;
 
@@ -51,21 +53,21 @@ public class LegacyMasterProcessChannelProcessorFactory
     {
         if ( !canUse( channelConfig ) )
         {
-            throw new MalformedURLException( "Unknown chanel string " + channelConfig );
+            throw new MalformedURLException( "Unknown channel string " + channelConfig );
         }
     }
 
     @Override
-    public MasterProcessChannelDecoder createDecoder()
+    public MasterProcessChannelDecoder createDecoder( @Nonnull ForkNodeArguments forkingArguments )
     {
-        return new LegacyMasterProcessChannelDecoder( newBufferedChannel( System.in ) );
+        return new CommandChannelDecoder( newBufferedChannel( System.in ), forkingArguments );
     }
 
     @Override
-    public MasterProcessChannelEncoder createEncoder()
+    public MasterProcessChannelEncoder createEncoder( @Nonnull ForkNodeArguments forkingArguments )
     {
         WritableBufferedByteChannel channel = newBufferedChannel( System.out );
         schedulePeriodicFlusher( FLUSH_PERIOD_MILLIS, channel );
-        return new LegacyMasterProcessChannelEncoder( channel );
+        return new EventChannelEncoder( channel );
     }
 }
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 0bebeb4..6232209 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
@@ -21,8 +21,10 @@ package org.apache.maven.surefire.booter.spi;
 
 import org.apache.maven.surefire.api.booter.MasterProcessChannelDecoder;
 import org.apache.maven.surefire.api.booter.MasterProcessChannelEncoder;
+import org.apache.maven.surefire.api.fork.ForkNodeArguments;
 import org.apache.maven.surefire.api.util.internal.WritableBufferedByteChannel;
 
+import javax.annotation.Nonnull;
 import java.io.IOException;
 import java.net.InetSocketAddress;
 import java.net.MalformedURLException;
@@ -31,6 +33,7 @@ import java.net.URI;
 import java.net.URISyntaxException;
 import java.nio.ByteBuffer;
 import java.nio.channels.AsynchronousSocketChannel;
+import java.nio.channels.ReadableByteChannel;
 import java.util.StringTokenizer;
 import java.util.concurrent.ExecutionException;
 
@@ -69,7 +72,7 @@ public class SurefireMasterProcessChannelProcessorFactory
     {
         if ( !canUse( channelConfig ) )
         {
-            throw new MalformedURLException( "Unknown chanel string " + channelConfig );
+            throw new MalformedURLException( "Unknown channel string " + channelConfig );
         }
 
         try
@@ -97,17 +100,18 @@ public class SurefireMasterProcessChannelProcessorFactory
     }
 
     @Override
-    public MasterProcessChannelDecoder createDecoder()
+    public MasterProcessChannelDecoder createDecoder( @Nonnull ForkNodeArguments forkingArguments )
     {
-        return new LegacyMasterProcessChannelDecoder( newBufferedChannel( newInputStream( clientSocketChannel ) ) );
+        ReadableByteChannel bufferedChannel = newBufferedChannel( newInputStream( clientSocketChannel ) );
+        return new CommandChannelDecoder( bufferedChannel, forkingArguments );
     }
 
     @Override
-    public MasterProcessChannelEncoder createEncoder()
+    public MasterProcessChannelEncoder createEncoder( @Nonnull ForkNodeArguments forkingArguments )
     {
         WritableBufferedByteChannel channel = newBufferedChannel( newOutputStream( clientSocketChannel ) );
         schedulePeriodicFlusher( FLUSH_PERIOD_MILLIS, channel );
-        return new LegacyMasterProcessChannelEncoder( channel );
+        return new EventChannelEncoder( channel );
     }
 
     @Override
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/stream/CommandDecoder.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/stream/CommandDecoder.java
new file mode 100644
index 0000000..b660147
--- /dev/null
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/stream/CommandDecoder.java
@@ -0,0 +1,276 @@
+package org.apache.maven.surefire.booter.stream;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.api.booter.Command;
+import org.apache.maven.surefire.api.booter.MasterProcessCommand;
+import org.apache.maven.surefire.api.booter.Shutdown;
+import org.apache.maven.surefire.api.fork.ForkNodeArguments;
+import org.apache.maven.surefire.api.report.RunMode;
+import org.apache.maven.surefire.api.stream.AbstractStreamDecoder;
+import org.apache.maven.surefire.api.stream.MalformedChannelException;
+import org.apache.maven.surefire.api.stream.SegmentType;
+
+import javax.annotation.Nonnull;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.channels.ReadableByteChannel;
+import java.util.concurrent.Callable;
+import java.util.concurrent.FutureTask;
+
+import static org.apache.maven.surefire.api.booter.Command.BYE_ACK;
+import static org.apache.maven.surefire.api.booter.Command.NOOP;
+import static org.apache.maven.surefire.api.booter.Command.SKIP_SINCE_NEXT_TEST;
+import static org.apache.maven.surefire.api.booter.Command.TEST_SET_FINISHED;
+import static org.apache.maven.surefire.api.booter.Command.toRunClass;
+import static org.apache.maven.surefire.api.booter.Command.toShutdown;
+import static org.apache.maven.surefire.api.booter.Constants.MAGIC_NUMBER_FOR_COMMANDS_BYTES;
+import static org.apache.maven.surefire.api.booter.MasterProcessCommand.COMMAND_TYPES;
+import static org.apache.maven.surefire.api.report.RunMode.RUN_MODES;
+import static org.apache.maven.surefire.api.stream.SegmentType.DATA_STRING;
+import static org.apache.maven.surefire.api.stream.SegmentType.END_OF_FRAME;
+import static org.apache.maven.surefire.api.stream.SegmentType.RUN_MODE;
+import static org.apache.maven.surefire.api.stream.SegmentType.STRING_ENCODING;
+
+/**
+ *
+ */
+public class CommandDecoder extends AbstractStreamDecoder<Command, MasterProcessCommand, SegmentType>
+{
+    private static final int DEBUG_SINK_BUFFER_SIZE = 64 * 1024;
+    private static final int NO_POSITION = -1;
+
+    private static final SegmentType[] COMMAND_WITHOUT_DATA = new SegmentType[] {
+        END_OF_FRAME
+    };
+
+    private static final SegmentType[] COMMAND_WITH_RUNNABLE_STRING = new SegmentType[] {
+        RUN_MODE,
+        STRING_ENCODING,
+        DATA_STRING,
+        END_OF_FRAME
+    };
+
+    private static final SegmentType[] COMMAND_WITH_ONE_STRING = new SegmentType[] {
+        STRING_ENCODING,
+        DATA_STRING,
+        END_OF_FRAME
+    };
+
+    private final ForkNodeArguments arguments;
+    private final OutputStream debugSink;
+
+    public CommandDecoder( @Nonnull ReadableByteChannel channel,
+                           @Nonnull ForkNodeArguments arguments )
+    {
+        super( channel, arguments, COMMAND_TYPES );
+        this.arguments = arguments;
+        debugSink = newDebugSink();
+    }
+
+    @Override
+    public Command decode( @Nonnull Memento memento ) throws IOException, MalformedChannelException
+    {
+        try
+        {
+            MasterProcessCommand commandType = readMessageType( memento );
+            if ( commandType == null )
+            {
+                throw new MalformedFrameException( memento.getLine().getPositionByteBuffer(),
+                    memento.getByteBuffer().position() );
+            }
+            RunMode runMode = null;
+            for ( SegmentType segmentType : nextSegmentType( commandType ) )
+            {
+                switch ( segmentType )
+                {
+                    case RUN_MODE:
+                        runMode = RUN_MODES.get( readSegment( memento ) );
+                        break;
+                    case STRING_ENCODING:
+                        memento.setCharset( readCharset( memento ) );
+                        break;
+                    case DATA_STRING:
+                        memento.getData().add( readString( memento ) );
+                        break;
+                    case DATA_INTEGER:
+                        memento.getData().add( readInteger( memento ) );
+                        break;
+                    case END_OF_FRAME:
+                        memento.getLine().setPositionByteBuffer( memento.getByteBuffer().position() );
+                        return toMessage( commandType, runMode, memento );
+                    default:
+                        memento.getLine().setPositionByteBuffer( NO_POSITION );
+                        arguments.dumpStreamText( "Unknown enum ("
+                            + SegmentType.class.getSimpleName()
+                            + ") "
+                            + segmentType );
+                }
+            }
+        }
+        catch ( MalformedFrameException e )
+        {
+            if ( e.hasValidPositions() )
+            {
+                int length = e.readTo() - e.readFrom();
+                memento.getLine().write( memento.getByteBuffer(), e.readFrom(), length );
+            }
+            return null;
+        }
+        catch ( RuntimeException e )
+        {
+            getArguments().dumpStreamException( e );
+            return null;
+        }
+        catch ( IOException e )
+        {
+            if ( !( e.getCause() instanceof InterruptedException ) )
+            {
+                printRemainingStream( memento );
+            }
+            throw e;
+        }
+        finally
+        {
+            memento.reset();
+        }
+
+        throw new MalformedChannelException();
+    }
+
+    @Nonnull
+    @Override
+    protected final byte[] getEncodedMagicNumber()
+    {
+        return MAGIC_NUMBER_FOR_COMMANDS_BYTES;
+    }
+
+    @Nonnull
+    @Override
+    protected SegmentType[] nextSegmentType( @Nonnull MasterProcessCommand commandType )
+    {
+        switch ( commandType )
+        {
+            case NOOP:
+            case BYE_ACK:
+            case SKIP_SINCE_NEXT_TEST:
+            case TEST_SET_FINISHED:
+                return COMMAND_WITHOUT_DATA;
+            case RUN_CLASS:
+                return COMMAND_WITH_RUNNABLE_STRING;
+            case SHUTDOWN:
+                return COMMAND_WITH_ONE_STRING;
+            default:
+                throw new IllegalArgumentException( "Unknown enum " + commandType );
+        }
+    }
+
+    @Nonnull
+    @Override
+    protected Command toMessage( @Nonnull MasterProcessCommand commandType, RunMode runMode, @Nonnull Memento memento )
+        throws MalformedFrameException
+    {
+        switch ( commandType )
+        {
+            case NOOP:
+                checkArguments( memento, 0 );
+                return NOOP;
+            case BYE_ACK:
+                checkArguments( memento, 0 );
+                return BYE_ACK;
+            case SKIP_SINCE_NEXT_TEST:
+                checkArguments( memento, 0 );
+                return SKIP_SINCE_NEXT_TEST;
+            case TEST_SET_FINISHED:
+                checkArguments( memento, 0 );
+                return TEST_SET_FINISHED;
+            case RUN_CLASS:
+                checkArguments( memento, 1 );
+                return toRunClass( (String) memento.getData().get( 0 ) );
+            case SHUTDOWN:
+                checkArguments( memento, 1 );
+                return toShutdown( Shutdown.parameterOf( (String) memento.getData().get( 0 ) ) );
+            default:
+                throw new IllegalArgumentException( "Missing a branch for the event type " + commandType );
+        }
+    }
+
+    @Override
+    protected void debugStream( byte[] array, int position, int remaining )
+    {
+        if ( debugSink == null )
+        {
+            return;
+        }
+
+        try
+        {
+            debugSink.write( array, position, remaining );
+            debugSink.flush();
+        }
+        catch ( IOException e )
+        {
+            // logger file was deleted
+            // System.out is already used by the stream in this decoder
+        }
+    }
+
+    private OutputStream newDebugSink()
+    {
+        final File sink = arguments.getCommandStreamBinaryFile();
+        if ( sink == null )
+        {
+            return null;
+        }
+
+        try
+        {
+            OutputStream fos = new FileOutputStream( sink, true );
+            final OutputStream os = new BufferedOutputStream( fos, DEBUG_SINK_BUFFER_SIZE );
+            Runtime.getRuntime().addShutdownHook( new Thread( new FutureTask<>( new Callable<Void>()
+            {
+                @Override
+                public Void call() throws Exception
+                {
+                    os.close();
+                    return null;
+                }
+            } ) ) );
+            return os;
+        }
+        catch ( FileNotFoundException e )
+        {
+            return null;
+        }
+    }
+
+    @Override
+    public void close() throws IOException
+    {
+        if ( debugSink != null )
+        {
+            debugSink.close();
+        }
+    }
+}
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/stream/EventEncoder.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/stream/EventEncoder.java
new file mode 100644
index 0000000..f982017
--- /dev/null
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/stream/EventEncoder.java
@@ -0,0 +1,78 @@
+package org.apache.maven.surefire.booter.stream;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.api.booter.ForkedProcessEventType;
+import org.apache.maven.surefire.api.stream.AbstractStreamEncoder;
+import org.apache.maven.surefire.api.util.internal.WritableBufferedByteChannel;
+
+import javax.annotation.Nonnull;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+
+import static org.apache.maven.surefire.api.booter.Constants.DEFAULT_STREAM_ENCODING;
+import static org.apache.maven.surefire.api.booter.Constants.DEFAULT_STREAM_ENCODING_BYTES;
+import static org.apache.maven.surefire.api.booter.Constants.MAGIC_NUMBER_FOR_EVENTS_BYTES;
+
+/**
+ *
+ */
+public class EventEncoder extends AbstractStreamEncoder<ForkedProcessEventType>
+{
+    public EventEncoder( WritableBufferedByteChannel out )
+    {
+        super( out );
+    }
+
+    @Nonnull
+    @Override
+    protected final byte[] getEncodedMagicNumber()
+    {
+        return MAGIC_NUMBER_FOR_EVENTS_BYTES;
+    }
+
+    @Nonnull
+    @Override
+    protected final byte[] enumToByteArray( @Nonnull ForkedProcessEventType e )
+    {
+        return e.getOpcodeBinary();
+    }
+
+    @Nonnull
+    @Override
+    protected final byte[] getEncodedCharsetName()
+    {
+        return DEFAULT_STREAM_ENCODING_BYTES;
+    }
+
+    @Nonnull
+    @Override
+    protected final Charset getCharset()
+    {
+        return DEFAULT_STREAM_ENCODING;
+    }
+
+    @Nonnull
+    @Override
+    protected final CharsetEncoder newCharsetEncoder()
+    {
+        return getCharset().newEncoder();
+    }
+}
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java
index 5bc5ffe..2cb7267 100644
--- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java
@@ -23,8 +23,9 @@ import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.plugin.surefire.log.api.NullConsoleLogger;
 import org.apache.maven.surefire.api.booter.MasterProcessChannelDecoder;
 import org.apache.maven.surefire.api.booter.Shutdown;
-import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelDecoder;
-import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder;
+import org.apache.maven.surefire.api.fork.ForkNodeArguments;
+import org.apache.maven.surefire.booter.spi.CommandChannelDecoder;
+import org.apache.maven.surefire.booter.spi.EventChannelEncoder;
 import org.apache.maven.surefire.api.testset.TestSetFailedException;
 import org.apache.maven.surefire.api.util.internal.WritableBufferedByteChannel;
 import org.junit.After;
@@ -45,6 +46,7 @@ import java.util.concurrent.FutureTask;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
+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.fest.assertions.Assertions.assertThat;
@@ -61,6 +63,7 @@ import static org.junit.Assert.fail;
  * @since 2.19
  */
 @RunWith( NewClassLoaderRunner.class )
+@SuppressWarnings( "checkstyle:magicnumber" )
 public class CommandReaderTest
 {
     private static final long DELAY = 200L;
@@ -93,7 +96,9 @@ public class CommandReaderTest
         InputStream realInputStream = new SystemInputStream();
         addTestToPipeline( getClass().getName() );
         ConsoleLogger logger = new NullConsoleLogger();
-        MasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( realInputStream ) );
+        ForkNodeArguments args = new ForkedNodeArg( 1, false );
+        MasterProcessChannelDecoder decoder =
+            new CommandChannelDecoder( newChannel( realInputStream ), args );
         reader = new CommandReader( decoder, Shutdown.DEFAULT, logger );
     }
 
@@ -106,7 +111,7 @@ public class CommandReaderTest
     @Test
     public void readJustOneClass()
     {
-        Iterator<String> it = reader.getIterableClasses( new LegacyMasterProcessChannelEncoder( nul() ) ).iterator();
+        Iterator<String> it = reader.getIterableClasses( new EventChannelEncoder( nul() ) ).iterator();
         assertTrue( it.hasNext() );
         assertThat( it.next(), is( getClass().getName() ) );
         reader.stop();
@@ -125,7 +130,7 @@ public class CommandReaderTest
     @Test
     public void manyClasses()
     {
-        Iterator<String> it1 = reader.getIterableClasses( new LegacyMasterProcessChannelEncoder( nul() ) ).iterator();
+        Iterator<String> it1 = reader.getIterableClasses( new EventChannelEncoder( nul() ) ).iterator();
         assertThat( it1.next(), is( getClass().getName() ) );
         addTestToPipeline( A.class.getName() );
         assertThat( it1.next(), is( A.class.getName() ) );
@@ -141,7 +146,7 @@ public class CommandReaderTest
     @Test
     public void twoIterators() throws Exception
     {
-        Iterator<String> it1 = reader.getIterableClasses( new LegacyMasterProcessChannelEncoder( nul() ) ).iterator();
+        Iterator<String> it1 = reader.getIterableClasses( new EventChannelEncoder( nul() ) ).iterator();
 
         assertThat( it1.next(), is( getClass().getName() ) );
         addTestToPipeline( A.class.getName() );
@@ -174,8 +179,7 @@ public class CommandReaderTest
             @Override
             public void run()
             {
-                Iterator<String> it =
-                    reader.getIterableClasses( new LegacyMasterProcessChannelEncoder( nul() ) ).iterator();
+                Iterator<String> it = reader.getIterableClasses( new EventChannelEncoder( nul() ) ).iterator();
                 assertThat( it.next(), is( CommandReaderTest.class.getName() ) );
             }
         };
@@ -203,7 +207,7 @@ public class CommandReaderTest
             public void run()
             {
                 Iterator<String> it =
-                    reader.getIterableClasses( new LegacyMasterProcessChannelEncoder( nul() ) ).iterator();
+                    reader.getIterableClasses( new EventChannelEncoder( nul() ) ).iterator();
                 assertThat( it.next(), is( CommandReaderTest.class.getName() ) );
                 counter.countDown();
                 assertThat( it.next(), is( Foo.class.getName() ) );
@@ -250,7 +254,25 @@ public class CommandReaderTest
 
     private void addTestToPipeline( String cls )
     {
-        for ( byte cmdByte : ( ":maven-surefire-command:run-testclass:" + cls + ":" ).getBytes() )
+        int clsLength = cls.length();
+        String cmd = new StringBuilder( 512 )
+            .append( ":maven-surefire-command:" )
+            .append( (char) 13 )
+            .append( ":run-testclass:" )
+            .append( (char) 10 )
+            .append( ":normal-run:" )
+            .append( (char) 5 )
+            .append( ":UTF-8:" )
+            .append( (char) ( clsLength >> 24 ) )
+            .append( (char) ( ( clsLength >> 16 ) & 0xff ) )
+            .append( (char) ( ( clsLength >> 8 ) & 0xff ) )
+            .append( (char) ( clsLength & 0xff ) )
+            .append( ":" )
+            .append( cls )
+            .append( ":" )
+            .toString();
+
+        for ( byte cmdByte : cmd.getBytes( US_ASCII ) )
         {
             blockingStream.add( cmdByte );
         }
@@ -258,7 +280,7 @@ public class CommandReaderTest
 
     private void addEndOfPipeline()
     {
-        for ( byte cmdByte : ":maven-surefire-command:testset-finished:".getBytes() )
+        for ( byte cmdByte : ( ":maven-surefire-command:" + (char) 16 + ":testset-finished:" ).getBytes( US_ASCII ) )
         {
             blockingStream.add( cmdByte );
         }
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 c1140c8..0f3d8db 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,12 @@ 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.fork.ForkNodeArguments;
 import org.apache.maven.surefire.api.report.StackTraceWriter;
 import org.apache.maven.surefire.api.util.internal.WritableBufferedByteChannel;
 import org.apache.maven.surefire.booter.spi.AbstractMasterProcessChannelProcessorFactory;
-import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelDecoder;
-import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder;
+import org.apache.maven.surefire.booter.spi.CommandChannelDecoder;
+import org.apache.maven.surefire.booter.spi.EventChannelEncoder;
 import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelProcessorFactory;
 import org.apache.maven.surefire.booter.spi.SurefireMasterProcessChannelProcessorFactory;
 import org.apache.maven.surefire.shared.utils.cli.ShutdownHookUtils;
@@ -44,6 +45,7 @@ import org.powermock.core.classloader.annotations.PowerMockIgnore;
 import org.powermock.core.classloader.annotations.PrepareForTest;
 import org.powermock.modules.junit4.PowerMockRunner;
 
+import javax.annotation.Nonnull;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.net.InetSocketAddress;
@@ -86,7 +88,7 @@ import static org.powermock.reflect.Whitebox.setInternalState;
 @PrepareForTest( {
                      PpidChecker.class,
                      ForkedBooter.class,
-                     LegacyMasterProcessChannelEncoder.class,
+                     EventChannelEncoder.class,
                      ShutdownHookUtils.class
 } )
 @PowerMockIgnore( { "org.jacoco.agent.rt.*", "com.vladium.emma.rt.*" } )
@@ -105,7 +107,7 @@ public class ForkedBooterMockTest
     private MasterProcessChannelProcessorFactory channelProcessorFactory;
 
     @Mock
-    private LegacyMasterProcessChannelEncoder eventChannel;
+    private EventChannelEncoder eventChannel;
 
     @Captor
     private ArgumentCaptor<StackTraceWriter> capturedStackTraceWriter;
@@ -292,10 +294,11 @@ public class ForkedBooterMockTest
 
             factory.connect( "pipe://3" );
 
-            MasterProcessChannelDecoder decoder = factory.createDecoder();
-            assertThat( decoder ).isInstanceOf( LegacyMasterProcessChannelDecoder.class );
-            MasterProcessChannelEncoder encoder = factory.createEncoder();
-            assertThat( encoder ).isInstanceOf( LegacyMasterProcessChannelEncoder.class );
+            ForkNodeArguments args = new ForkedNodeArg( 1, false );
+            MasterProcessChannelDecoder decoder = factory.createDecoder( args );
+            assertThat( decoder ).isInstanceOf( CommandChannelDecoder.class );
+            MasterProcessChannelEncoder encoder = factory.createEncoder( args );
+            assertThat( encoder ).isInstanceOf( EventChannelEncoder.class );
         }
     }
 
@@ -318,13 +321,13 @@ public class ForkedBooterMockTest
             }
 
             @Override
-            public MasterProcessChannelDecoder createDecoder()
+            public MasterProcessChannelDecoder createDecoder( @Nonnull  ForkNodeArguments args )
             {
                 return null;
             }
 
             @Override
-            public MasterProcessChannelEncoder createEncoder()
+            public MasterProcessChannelEncoder createEncoder( @Nonnull ForkNodeArguments args )
             {
                 return null;
             }
@@ -411,13 +414,13 @@ public class ForkedBooterMockTest
                 } );
 
                 factory.connect( "tcp://localhost:" + serverPort );
-
-                MasterProcessChannelDecoder decoder = factory.createDecoder();
+                ForkNodeArguments args = new ForkedNodeArg( 1, false );
+                MasterProcessChannelDecoder decoder = factory.createDecoder( args );
                 assertThat( decoder )
-                    .isInstanceOf( LegacyMasterProcessChannelDecoder.class );
-                MasterProcessChannelEncoder encoder = factory.createEncoder();
+                    .isInstanceOf( CommandChannelDecoder.class );
+                MasterProcessChannelEncoder encoder = factory.createEncoder( args );
                 assertThat( encoder )
-                    .isInstanceOf( LegacyMasterProcessChannelEncoder.class );
+                    .isInstanceOf( EventChannelEncoder.class );
             }
         }
     }
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/JUnit4SuiteTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/JUnit4SuiteTest.java
index 280774e..6c14e0e 100644
--- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/JUnit4SuiteTest.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/JUnit4SuiteTest.java
@@ -23,8 +23,8 @@ import junit.framework.JUnit4TestAdapter;
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
-import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelDecoderTest;
-import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoderTest;
+import org.apache.maven.surefire.booter.spi.CommandChannelDecoderTest;
+import org.apache.maven.surefire.booter.spi.EventChannelEncoderTest;
 
 /**
  * Adapt the JUnit4 tests which use only annotations to the JUnit3 test suite.
@@ -46,8 +46,8 @@ public class JUnit4SuiteTest extends TestCase
         suite.addTest( new JUnit4TestAdapter( BooterDeserializerTest.class ) );
         suite.addTestSuite( ClasspathTest.class );
         suite.addTestSuite( PropertiesWrapperTest.class );
-        suite.addTest( new JUnit4TestAdapter( LegacyMasterProcessChannelDecoderTest.class ) );
-        suite.addTest( new JUnit4TestAdapter( LegacyMasterProcessChannelEncoderTest.class ) );
+        suite.addTest( new JUnit4TestAdapter( CommandChannelDecoderTest.class ) );
+        suite.addTest( new JUnit4TestAdapter( EventChannelEncoderTest.class ) );
         suite.addTestSuite( SurefireReflectorTest.class );
         return suite;
     }
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/CommandChannelDecoderTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/CommandChannelDecoderTest.java
new file mode 100644
index 0000000..f31d9d8
--- /dev/null
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/CommandChannelDecoderTest.java
@@ -0,0 +1,519 @@
+package org.apache.maven.surefire.booter.spi;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.surefire.api.booter.Command;
+import org.apache.maven.surefire.api.booter.DumpErrorSingleton;
+import org.apache.maven.surefire.api.booter.Shutdown;
+import org.apache.maven.surefire.api.fork.ForkNodeArguments;
+import org.apache.maven.surefire.booter.ForkedNodeArg;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import javax.annotation.Nonnull;
+import java.io.ByteArrayInputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import static java.nio.channels.Channels.newChannel;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.maven.surefire.api.booter.MasterProcessCommand.BYE_ACK;
+import static org.apache.maven.surefire.api.booter.MasterProcessCommand.NOOP;
+import static org.apache.maven.surefire.api.booter.MasterProcessCommand.RUN_CLASS;
+import static org.apache.maven.surefire.api.booter.MasterProcessCommand.SHUTDOWN;
+import static org.apache.maven.surefire.api.booter.MasterProcessCommand.SKIP_SINCE_NEXT_TEST;
+import static org.apache.maven.surefire.api.booter.MasterProcessCommand.TEST_SET_FINISHED;
+import static org.apache.maven.surefire.api.booter.Shutdown.DEFAULT;
+import static org.apache.maven.surefire.api.booter.Shutdown.EXIT;
+import static org.apache.maven.surefire.api.booter.Shutdown.KILL;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+/**
+ * Tests for {@link CommandChannelDecoder}.
+ */
+@SuppressWarnings( "checkstyle:magicnumber" )
+public class CommandChannelDecoderTest
+{
+    @Rule
+    public final TemporaryFolder tempFolder = new TemporaryFolder();
+
+    private File reportsDir;
+    private String dumpFileName;
+
+    @Before
+    public void initTmpFile() throws IOException
+    {
+        File tmp = tempFolder.newFile();
+        reportsDir = tmp.getParentFile();
+        dumpFileName = tmp.getName();
+    }
+
+    @Test
+    public void testDecoderRunClass() throws IOException
+    {
+        assertEquals( String.class, RUN_CLASS.getDataType() );
+        byte[] encoded = new StringBuilder( 512 )
+            .append( ":maven-surefire-command:" )
+            .append( (char) 13 )
+            .append( ":run-testclass:" )
+            .append( (char) 10 )
+            .append( ":normal-run:" )
+            .append( (char) 5 )
+            .append( ":UTF-8:" )
+            .append( (char) 0 )
+            .append( (char) 0 )
+            .append( (char) 0 )
+            .append( (char) 8 )
+            .append( ":" )
+            .append( "pkg.Test" )
+            .append( ":" )
+            .toString()
+            .getBytes( UTF_8 );
+        InputStream is = new ByteArrayInputStream( encoded );
+        DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
+        ForkNodeArguments args = new ForkedNodeArg( 1, false );
+        CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
+        Command command = decoder.decode();
+        assertThat( command.getCommandType() ).isSameAs( RUN_CLASS );
+        assertThat( command.getData() ).isEqualTo( "pkg.Test" );
+    }
+
+    @Test
+    public void testDecoderTestsetFinished() throws IOException
+    {
+        Command command = Command.TEST_SET_FINISHED;
+        assertThat( command.getCommandType() ).isSameAs( TEST_SET_FINISHED );
+        assertEquals( Void.class, TEST_SET_FINISHED.getDataType() );
+        byte[] encoded = ":maven-surefire-command:\u0010:testset-finished:".getBytes();
+        ByteArrayInputStream is = new ByteArrayInputStream( encoded );
+        DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
+        ForkNodeArguments args = new ForkedNodeArg( 1, false );
+        CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
+        command = decoder.decode();
+        assertThat( command.getCommandType() ).isSameAs( TEST_SET_FINISHED );
+        assertNull( command.getData() );
+    }
+
+    @Test
+    public void testDecoderSkipSinceNextTest() throws IOException
+    {
+        Command command = Command.SKIP_SINCE_NEXT_TEST;
+        assertThat( command.getCommandType() ).isSameAs( SKIP_SINCE_NEXT_TEST );
+        assertEquals( Void.class, SKIP_SINCE_NEXT_TEST.getDataType() );
+        byte[] encoded = ":maven-surefire-command:\u0014:skip-since-next-test:".getBytes();
+        ByteArrayInputStream is = new ByteArrayInputStream( encoded );
+        DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
+        ForkNodeArguments args = new ForkedNodeArg( 1, false );
+        CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
+        command = decoder.decode();
+        assertThat( command.getCommandType() ).isSameAs( SKIP_SINCE_NEXT_TEST );
+        assertNull( command.getData() );
+    }
+
+    @Test
+    public void testDecoderShutdownWithExit() throws IOException
+    {
+        Shutdown shutdownType = EXIT;
+        assertEquals( String.class, SHUTDOWN.getDataType() );
+        byte[] encoded = ( ":maven-surefire-command:\u0008:shutdown:\u0005:UTF-8:\u0000\u0000\u0000\u0004:"
+            + shutdownType.getParam() + ":" ).getBytes();
+        ByteArrayInputStream is = new ByteArrayInputStream( encoded );
+        DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
+        ForkNodeArguments args = new ForkedNodeArg( 1, false );
+        CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
+        Command command = decoder.decode();
+        assertThat( command.getCommandType() ).isSameAs( SHUTDOWN );
+        assertThat( command.getData() ).isEqualTo( shutdownType.name() );
+    }
+
+    @Test
+    public void testDecoderShutdownWithKill() throws IOException
+    {
+        Shutdown shutdownType = KILL;
+        assertEquals( String.class, SHUTDOWN.getDataType() );
+        byte[] encoded = ( ":maven-surefire-command:\u0008:shutdown:\u0005:UTF-8:\u0000\u0000\u0000\u0004:"
+            + shutdownType.getParam() + ":" ).getBytes();
+        ByteArrayInputStream is = new ByteArrayInputStream( encoded );
+        DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
+        ForkNodeArguments args = new ForkedNodeArg( 1, false );
+        CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
+        Command command = decoder.decode();
+        assertThat( command.getCommandType() ).isSameAs( SHUTDOWN );
+        assertThat( command.getData() ).isEqualTo( shutdownType.name() );
+    }
+
+    @Test
+    public void testDecoderShutdownWithDefault() throws IOException
+    {
+        Shutdown shutdownType = DEFAULT;
+        assertEquals( String.class, SHUTDOWN.getDataType() );
+        byte[] encoded = ( ":maven-surefire-command:\u0008:shutdown:\u0005:UTF-8:\u0000\u0000\u0000\u0007:"
+            + shutdownType.getParam() + ":" ).getBytes();
+        ByteArrayInputStream is = new ByteArrayInputStream( encoded );
+        DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
+        ForkNodeArguments args = new ForkedNodeArg( 1, false );
+        CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
+        Command command = decoder.decode();
+        assertThat( command.getCommandType() ).isSameAs( SHUTDOWN );
+        assertThat( command.getData() ).isEqualTo( shutdownType.name() );
+    }
+
+    @Test
+    public void testDecoderNoop() throws IOException
+    {
+        assertThat( NOOP ).isSameAs( Command.NOOP.getCommandType() );
+        assertEquals( Void.class, NOOP.getDataType() );
+        byte[] encoded = ":maven-surefire-command:\u0004:noop:".getBytes();
+        ByteArrayInputStream is = new ByteArrayInputStream( encoded );
+        DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
+        ForkNodeArguments args = new ForkedNodeArg( 1, false );
+        CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
+        Command command = decoder.decode();
+        assertThat( command.getCommandType() ).isSameAs( NOOP );
+        assertNull( command.getData() );
+    }
+
+    @Test
+    public void shouldIgnoreDamagedStream() throws IOException
+    {
+        assertThat( BYE_ACK ).isSameAs( Command.BYE_ACK.getCommandType() );
+        assertEquals( Void.class, BYE_ACK.getDataType() );
+        byte[] encoded = ":maven-surefire-command:\u0007:bye-ack:".getBytes();
+        byte[] streamContent = ( "<something>" + new String( encoded ) + "<damaged>" ).getBytes();
+        ByteArrayInputStream is = new ByteArrayInputStream( streamContent );
+        DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
+        ForkNodeArguments args = new ForkedNodeArg( 1, false );
+        CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
+        Command command = decoder.decode();
+        assertThat( command.getCommandType() ).isSameAs( BYE_ACK );
+        assertNull( command.getData() );
+    }
+
+    @Test
+    public void shouldIgnoreDamagedHeader() throws IOException
+    {
+        assertThat( BYE_ACK ).isSameAs( Command.BYE_ACK.getCommandType() );
+        assertEquals( Void.class, BYE_ACK.getDataType() );
+        byte[] encoded = ":maven-surefire-command:\u0007:bye-ack:".getBytes();
+        byte[] streamContent = ( ":<damaged>:" + new String( encoded ) ).getBytes();
+        ByteArrayInputStream is = new ByteArrayInputStream( streamContent );
+        DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
+        ForkNodeArguments args = new ForkedNodeArg( 1, false );
+        CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
+        Command command = decoder.decode();
+        assertThat( command.getCommandType() ).isSameAs( BYE_ACK );
+        assertNull( command.getData() );
+    }
+
+    @Test
+    public void testDecoderByeAck() throws IOException
+    {
+        assertThat( BYE_ACK ).isSameAs( Command.BYE_ACK.getCommandType() );
+        assertEquals( Void.class, BYE_ACK.getDataType() );
+        byte[] encoded = ":maven-surefire-command:\u0007:bye-ack:".getBytes();
+        ByteArrayInputStream is = new ByteArrayInputStream( encoded );
+        DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
+        ForkNodeArguments args = new ForkedNodeArg( 1, false );
+        CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
+        Command command = decoder.decode();
+        assertThat( command.getCommandType() ).isSameAs( BYE_ACK );
+        assertNull( command.getData() );
+    }
+
+    @Test
+    public void shouldDecodeTwoCommands() throws IOException
+    {
+        String cmd = ":maven-surefire-command:\u0007:bye-ack:\r\n:maven-surefire-command:\u0007:bye-ack:";
+        InputStream is = new ByteArrayInputStream( cmd.getBytes() );
+        DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
+        ForkNodeArguments args = new ForkedNodeArg( 1, false );
+        CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
+
+        Command command = decoder.decode();
+        assertThat( command.getCommandType() ).isEqualTo( BYE_ACK );
+        assertThat( command.getData() ).isNull();
+
+        command = decoder.decode();
+        assertThat( command.getCommandType() ).isEqualTo( BYE_ACK );
+        assertThat( command.getData() ).isNull();
+
+        decoder.close();
+    }
+
+    @Test( expected = EOFException.class )
+    public void testIncompleteCommand() throws IOException
+    {
+
+        ByteArrayInputStream is = new ByteArrayInputStream( ":maven-surefire-command:".getBytes() );
+        DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
+        ForkNodeArguments args = new ForkedNodeArg( 1, false );
+        CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
+        decoder.decode();
+        fail();
+    }
+
+    @Test( expected = EOFException.class )
+    public void testIncompleteCommandStart() throws IOException
+    {
+
+        ByteArrayInputStream is = new ByteArrayInputStream( new byte[] {':', '\r'} );
+        DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
+        ForkNodeArguments args = new ForkedNodeArg( 1, false );
+        CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
+        decoder.decode();
+        fail();
+    }
+
+    @Test( expected = EOFException.class )
+    public void shouldNotDecodeCorruptedCommand() throws IOException
+    {
+        String cmd = ":maven-surefire-command:\u0007:bye-ack ::maven-surefire-command:";
+        InputStream is = new ByteArrayInputStream( cmd.getBytes() );
+        DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
+        ForkNodeArguments args = new ForkedNodeArg( 1, false );
+        CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
+
+        decoder.decode();
+    }
+
+    @Test
+    public void shouldSkipCorruptedCommand() throws IOException
+    {
+        String cmd = ":maven-surefire-command:\0007:bye-ack\r\n::maven-surefire-command:\u0004:noop:";
+        InputStream is = new ByteArrayInputStream( cmd.getBytes() );
+        DumpErrorSingleton.getSingleton().init( reportsDir, dumpFileName );
+        ForkNodeArguments args = new ForkedNodeArg( 1, false );
+        CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( is ), args );
+
+        Command command = decoder.decode();
+        assertThat( command.getCommandType() ).isSameAs( NOOP );
+        assertNull( command.getData() );
+    }
+
+    @Test
+    public void testBinaryCommandStream() throws Exception
+    {
+        InputStream commands = getClass().getResourceAsStream( "/binary-commands/75171711-encoder.bin" );
+        ConsoleLoggerMock logger = new ConsoleLoggerMock( true, true, true, true );
+        ForkNodeArguments args = new ForkNodeArgumentsMock( logger, new File( "" ) );
+        CommandChannelDecoder decoder = new CommandChannelDecoder( newChannel( commands ), args );
+
+        Command command = decoder.decode();
+        assertThat( command ).isNotNull();
+        assertThat( command.getCommandType() ).isEqualTo( NOOP );
+        assertThat( command.getData() ).isNull();
+
+        command = decoder.decode();
+        assertThat( command ).isNotNull();
+        assertThat( command.getCommandType() ).isEqualTo( RUN_CLASS );
+        assertThat( command.getData() ).isEqualTo( "pkg.ATest" );
+
+        for ( int i = 0; i < 24; i++ )
+        {
+            command = decoder.decode();
+            assertThat( command ).isNotNull();
+            assertThat( command.getCommandType() ).isEqualTo( NOOP );
+            assertThat( command.getData() ).isNull();
+        }
+    }
+
+    /**
+     * Threadsafe impl. Mockito and Powermock are not thread-safe.
+     */
+    private static class ForkNodeArgumentsMock implements ForkNodeArguments
+    {
+        private final ConcurrentLinkedQueue<String> dumpStreamText = new ConcurrentLinkedQueue<>();
+        private final ConcurrentLinkedQueue<String> logWarningAtEnd = new ConcurrentLinkedQueue<>();
+        private final ConsoleLogger logger;
+        private final File dumpStreamTextFile;
+
+        ForkNodeArgumentsMock( ConsoleLogger logger, File dumpStreamTextFile )
+        {
+            this.logger = logger;
+            this.dumpStreamTextFile = dumpStreamTextFile;
+        }
+
+        @Nonnull
+        @Override
+        public String getSessionId()
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public int getForkChannelId()
+        {
+            return 0;
+        }
+
+        @Nonnull
+        @Override
+        public File dumpStreamText( @Nonnull String text )
+        {
+            dumpStreamText.add( text );
+            return dumpStreamTextFile;
+        }
+
+        @Nonnull
+        @Override
+        public File dumpStreamException( @Nonnull Throwable t )
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void logWarningAtEnd( @Nonnull String text )
+        {
+            logWarningAtEnd.add( text );
+        }
+
+        @Nonnull
+        @Override
+        public ConsoleLogger getConsoleLogger()
+        {
+            return logger;
+        }
+
+        boolean isCalled()
+        {
+            return !dumpStreamText.isEmpty() || !logWarningAtEnd.isEmpty();
+        }
+
+        @Override
+        public File getEventStreamBinaryFile()
+        {
+            return null;
+        }
+
+        @Override
+        public File getCommandStreamBinaryFile()
+        {
+            return null;
+        }
+    }
+
+    /**
+     * Threadsafe impl. Mockito and Powermock are not thread-safe.
+     */
+    private static class ConsoleLoggerMock implements ConsoleLogger
+    {
+        final ConcurrentLinkedQueue<String> debug = new ConcurrentLinkedQueue<>();
+        final ConcurrentLinkedQueue<String> info = new ConcurrentLinkedQueue<>();
+        final ConcurrentLinkedQueue<String> error = new ConcurrentLinkedQueue<>();
+        final boolean isDebug;
+        final boolean isInfo;
+        final boolean isWarning;
+        final boolean isError;
+        boolean called;
+        boolean isDebugEnabledCalled;
+        boolean isInfoEnabledCalled;
+
+        ConsoleLoggerMock( boolean isDebug, boolean isInfo, boolean isWarning, boolean isError )
+        {
+            this.isDebug = isDebug;
+            this.isInfo = isInfo;
+            this.isWarning = isWarning;
+            this.isError = isError;
+        }
+
+        @Override
+        public boolean isDebugEnabled()
+        {
+            isDebugEnabledCalled = true;
+            called = true;
+            return isDebug;
+        }
+
+        @Override
+        public void debug( String message )
+        {
+            debug.add( message );
+            called = true;
+        }
+
+        @Override
+        public boolean isInfoEnabled()
+        {
+            isInfoEnabledCalled = true;
+            called = true;
+            return isInfo;
+        }
+
+        @Override
+        public void info( String message )
+        {
+            info.add( message );
+            called = true;
+        }
+
+        @Override
+        public boolean isWarnEnabled()
+        {
+            called = true;
+            return isWarning;
+        }
+
+        @Override
+        public void warning( String message )
+        {
+            called = true;
+        }
+
+        @Override
+        public boolean isErrorEnabled()
+        {
+            called = true;
+            return isError;
+        }
+
+        @Override
+        public void error( String message )
+        {
+            error.add( message );
+            called = true;
+        }
+
+        @Override
+        public void error( String message, Throwable t )
+        {
+            called = true;
+        }
+
+        @Override
+        public void error( Throwable t )
+        {
+            called = true;
+        }
+
+        boolean isCalled()
+        {
+            return called;
+        }
+    }
+}
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoderTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/EventChannelEncoderTest.java
similarity index 81%
rename from surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoderTest.java
rename to surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/EventChannelEncoderTest.java
index 11f2d16..44abbf9 100644
--- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoderTest.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/EventChannelEncoderTest.java
@@ -29,152 +29,33 @@ import org.junit.Test;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
-import java.io.LineNumberReader;
 import java.io.PrintStream;
-import java.io.StringReader;
 import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
-import java.nio.charset.CharsetEncoder;
 import java.util.Map;
 import java.util.Map.Entry;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.Arrays.copyOfRange;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_BYE;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_DEBUG;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_ERROR;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_INFO;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_WARNING;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_JVM_EXIT_ERROR;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_NEXT_TEST;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDERR;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDERR_NEW_LINE;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDOUT;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDOUT_NEW_LINE;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STOP_ON_NEXT_TEST;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_SYSPROPS;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TESTSET_COMPLETED;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TESTSET_STARTING;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_ASSUMPTIONFAILURE;
 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_ERROR;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_FAILED;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_SKIPPED;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_STARTING;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_SUCCEEDED;
 import static org.apache.maven.surefire.api.report.RunMode.NORMAL_RUN;
 import static org.apache.maven.surefire.api.util.internal.Channels.newBufferedChannel;
-import static org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder.encode;
-import static org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder.encodeHeader;
-import static org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder.encodeMessage;
-import static org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder.encodeOpcode;
-import static org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder.estimateBufferLength;
 import static org.fest.assertions.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 /**
- * Test for {@link LegacyMasterProcessChannelEncoder}.
+ * Test for {@link EventChannelEncoder}.
  *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
  * @since 3.0.0-M4
  */
 @SuppressWarnings( { "checkstyle:linelength", "checkstyle:magicnumber" } )
-public class LegacyMasterProcessChannelEncoderTest
+public class EventChannelEncoderTest
 {
     private static final int ELAPSED_TIME = 102;
     private static final byte[] ELAPSED_TIME_HEXA = new byte[] {0, 0, 0, 0x66};
 
     @Test
-    public void shouldComputeStreamPreemptiveLength()
-    {
-        CharsetEncoder encoder = UTF_8.newEncoder();
-
-        // :maven-surefire-event:8:sys-prop:10:normal-run:5:UTF-8:0001:kkk:0001:vvv:
-        assertThat( estimateBufferLength( BOOTERCODE_SYSPROPS, NORMAL_RUN, encoder, 0, "k", "v" ) )
-            .isEqualTo( 72 );
-
-        // :maven-surefire-event:16:testset-starting:10:normal-run:5:UTF-8:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:X0001:0001:sss:0001:sss:0001:sss:
-        assertThat( estimateBufferLength( BOOTERCODE_TESTSET_STARTING, NORMAL_RUN, encoder, 1, "s", "s", "s", "s", "s", "s", "s", "s", "s" ) )
-            .isEqualTo( 149 );
-
-        // :maven-surefire-event:17:testset-completed:10:normal-run:5:UTF-8:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:X0001:0001:sss:0001:sss:0001:sss:
-        assertThat( estimateBufferLength( BOOTERCODE_TESTSET_COMPLETED, NORMAL_RUN, encoder, 1, "s", "s", "s", "s", "s", "s", "s", "s", "s" ) )
-            .isEqualTo( 150 );
-
-        // :maven-surefire-event:13:test-starting:10:normal-run:5:UTF-8:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:X0001:0001:sss:0001:sss:0001:sss:
-        assertThat( estimateBufferLength( BOOTERCODE_TEST_STARTING, NORMAL_RUN, encoder, 1, "s", "s", "s", "s", "s", "s", "s", "s", "s" ) )
-            .isEqualTo( 146 );
-
-        // :maven-surefire-event:14:test-succeeded:10:normal-run:5:UTF-8:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:X0001:0001:sss:0001:sss:0001:sss:
-        assertThat( estimateBufferLength( BOOTERCODE_TEST_SUCCEEDED, NORMAL_RUN, encoder, 1, "s", "s", "s", "s", "s", "s", "s", "s", "s" ) )
-            .isEqualTo( 147 );
-
-        // :maven-surefire-event:11:test-failed:10:normal-run:5:UTF-8:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:X0001:0001:sss:0001:sss:0001:sss:
-        assertThat( estimateBufferLength( BOOTERCODE_TEST_FAILED, NORMAL_RUN, encoder, 1, "s", "s", "s", "s", "s", "s", "s", "s", "s" ) )
-            .isEqualTo( 144 );
-
-        // :maven-surefire-event:12:test-skipped:10:normal-run:5:UTF-8:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:X0001:0001:sss:0001:sss:0001:sss:
-        assertThat( estimateBufferLength( BOOTERCODE_TEST_SKIPPED, NORMAL_RUN, encoder, 1, "s", "s", "s", "s", "s", "s", "s", "s", "s" ) )
-            .isEqualTo( 145 );
-
-        // :maven-surefire-event:10:test-error:10:normal-run:5:UTF-8:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:X0001:0001:sss:0001:sss:0001:sss:
-        assertThat( estimateBufferLength( BOOTERCODE_TEST_ERROR, NORMAL_RUN, encoder, 1, "s", "s", "s", "s", "s", "s", "s", "s", "s" ) )
-            .isEqualTo( 143 );
-
-        // :maven-surefire-event:23:test-assumption-failure:10:normal-run:5:UTF-8:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:0001:sss:X0001:0001:sss:0001:sss:0001:sss:
-        assertThat( estimateBufferLength( BOOTERCODE_TEST_ASSUMPTIONFAILURE, NORMAL_RUN, encoder, 1, "s", "s", "s", "s", "s", "s", "s", "s", "s" ) )
-            .isEqualTo( 156 );
-
-        // :maven-surefire-event:14:std-out-stream:10:normal-run:5:UTF-8:0001:sss:
-        assertThat( estimateBufferLength( BOOTERCODE_STDOUT, NORMAL_RUN, encoder, 0, "s" ) )
-            .isEqualTo( 69 );
-
-        // :maven-surefire-event:23:std-out-stream-new-line:10:normal-run:5:UTF-8:0001:sss:
-        assertThat( estimateBufferLength( BOOTERCODE_STDOUT_NEW_LINE, NORMAL_RUN, encoder, 0, "s" ) )
-            .isEqualTo( 78 );
-
-        // :maven-surefire-event:14:std-err-stream:10:normal-run:5:UTF-8:0001:sss:
-        assertThat( estimateBufferLength( BOOTERCODE_STDERR, NORMAL_RUN, encoder, 0, "s" ) )
-            .isEqualTo( 69 );
-
-        // :maven-surefire-event:23:std-err-stream-new-line:10:normal-run:5:UTF-8:0001:sss:
-        assertThat( estimateBufferLength( BOOTERCODE_STDERR_NEW_LINE, NORMAL_RUN, encoder, 0, "s" ) )
-            .isEqualTo( 78 );
-
-        // :maven-surefire-event:16:console-info-log:5:UTF-8:0001:sss:
-        assertThat( estimateBufferLength( BOOTERCODE_CONSOLE_INFO, null, encoder, 0, "s" ) )
-            .isEqualTo( 58 );
-
-        // :maven-surefire-event:17:console-debug-log:5:UTF-8:0001:sss:
-        assertThat( estimateBufferLength( BOOTERCODE_CONSOLE_DEBUG, null, encoder, 0, "s" ) )
-            .isEqualTo( 59 );
-
-        // :maven-surefire-event:19:console-warning-log:5:UTF-8:0001:sss:
-        assertThat( estimateBufferLength( BOOTERCODE_CONSOLE_WARNING, null, encoder, 0, "s" ) )
-            .isEqualTo( 61 );
-
-        // :maven-surefire-event:17:console-error-log:5:UTF-8:0001:sss:
-        assertThat( estimateBufferLength( BOOTERCODE_CONSOLE_ERROR, null, encoder, 0, "s" ) )
-            .isEqualTo( 59 );
-
-        // :maven-surefire-event:3:bye:
-        assertThat( estimateBufferLength( BOOTERCODE_BYE, null, null, 0 ) )
-            .isEqualTo( 28 );
-
-        // :maven-surefire-event:17:stop-on-next-test:
-        assertThat( estimateBufferLength( BOOTERCODE_STOP_ON_NEXT_TEST, null, null, 0 ) )
-            .isEqualTo( 42 );
-
-        // :maven-surefire-event:9:next-test:
-        assertThat( estimateBufferLength( BOOTERCODE_NEXT_TEST, null, null, 0 ) )
-            .isEqualTo( 34 );
-
-        // :maven-surefire-event:14:jvm-exit-error:
-        assertThat( estimateBufferLength( BOOTERCODE_JVM_EXIT_ERROR, null, null, 0 ) )
-            .isEqualTo( 39 );
-    }
-
-    @Test
     public void reportEntry() throws IOException
     {
         final String exceptionMessage = "msg";
@@ -198,7 +79,9 @@ public class LegacyMasterProcessChannelEncoderTest
         when( reportEntry.getSourceName() ).thenReturn( "pkg.MyTest" );
         when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
-        ByteBuffer encoded = encode( BOOTERCODE_TEST_ERROR, NORMAL_RUN, reportEntry, false );
+        Stream out = Stream.newStream();
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
+        ByteBuffer encoded = encoder.encode( BOOTERCODE_TEST_ERROR, NORMAL_RUN, reportEntry, false );
         ByteArrayOutputStream expectedFrame = new ByteArrayOutputStream();
         expectedFrame.write( ":maven-surefire-event:".getBytes( UTF_8 ) );
         expectedFrame.write( (byte) 10 );
@@ -251,7 +134,9 @@ public class LegacyMasterProcessChannelEncoderTest
         assertThat( toArray( encoded ) )
             .isEqualTo( expectedFrame.toByteArray() );
 
-        encoded = encode( BOOTERCODE_TEST_ERROR, NORMAL_RUN, reportEntry, true );
+        out = Stream.newStream();
+        encoder = new EventChannelEncoder( newBufferedChannel( out ) );
+        encoded = encoder.encode( BOOTERCODE_TEST_ERROR, NORMAL_RUN, reportEntry, true );
         expectedFrame = new ByteArrayOutputStream();
         expectedFrame.write( ":maven-surefire-event:".getBytes( UTF_8 ) );
         expectedFrame.write( (byte) 10 );
@@ -304,9 +189,8 @@ public class LegacyMasterProcessChannelEncoderTest
         assertThat( toArray( encoded ) )
             .isEqualTo( expectedFrame.toByteArray() );
 
-        Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
-
+        out = Stream.newStream();
+        encoder = new EventChannelEncoder( newBufferedChannel( out ) );
         encoder.testSetStarting( reportEntry, true );
         expectedFrame = new ByteArrayOutputStream();
         expectedFrame.write( ":maven-surefire-event:".getBytes( UTF_8 ) );
@@ -359,7 +243,7 @@ public class LegacyMasterProcessChannelEncoderTest
             .isEqualTo( expectedFrame.toByteArray() );
 
         out = Stream.newStream();
-        encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testSetStarting( reportEntry, false );
         expectedFrame = new ByteArrayOutputStream();
@@ -438,7 +322,7 @@ public class LegacyMasterProcessChannelEncoderTest
         when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testSetCompleted( reportEntry, false );
         ByteArrayOutputStream expectedFrame = new ByteArrayOutputStream();
@@ -517,7 +401,7 @@ public class LegacyMasterProcessChannelEncoderTest
         when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testStarting( reportEntry, true );
 
@@ -597,7 +481,7 @@ public class LegacyMasterProcessChannelEncoderTest
         when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testSucceeded( reportEntry, true );
         ByteArrayOutputStream expectedFrame = new ByteArrayOutputStream();
@@ -676,7 +560,7 @@ public class LegacyMasterProcessChannelEncoderTest
         when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testFailed( reportEntry, false );
         ByteArrayOutputStream expectedFrame = new ByteArrayOutputStream();
@@ -754,7 +638,7 @@ public class LegacyMasterProcessChannelEncoderTest
         when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testSkipped( reportEntry, false );
         ByteArrayOutputStream expectedFrame = new ByteArrayOutputStream();
@@ -832,7 +716,7 @@ public class LegacyMasterProcessChannelEncoderTest
         when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
         encoder.testError( reportEntry, false );
         ByteArrayOutputStream expectedFrame = new ByteArrayOutputStream();
         expectedFrame.write( ":maven-surefire-event:".getBytes( UTF_8 ) );
@@ -909,7 +793,7 @@ public class LegacyMasterProcessChannelEncoderTest
         when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testAssumptionFailure( reportEntry, false );
         ByteArrayOutputStream expectedFrame = new ByteArrayOutputStream();
@@ -966,7 +850,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testBye()
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
         encoder.bye();
 
@@ -980,7 +864,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testStopOnNextTest()
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
         encoder.stopOnNextTest();
 
@@ -993,7 +877,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testAcquireNextTest()
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
         encoder.acquireNextTest();
 
@@ -1005,41 +889,30 @@ public class LegacyMasterProcessChannelEncoderTest
     @Test
     public void testSendOpcode()
     {
-        CharsetEncoder encoder = UTF_8.newEncoder();
-        ByteBuffer result = ByteBuffer.allocate( 128 );
-        encodeOpcode( result, BOOTERCODE_TEST_ERROR, NORMAL_RUN );
-        assertThat( toString( result ) )
-                .isEqualTo( ":maven-surefire-event:" + (char) 10 + ":test-error:" + (char) 10 + ":normal-run:" );
-
-        result = ByteBuffer.allocate( 1024 );
-        encodeHeader( encoder, result, BOOTERCODE_TEST_ERROR, NORMAL_RUN );
-        assertThat( toString( result ) )
-                .isEqualTo( ":maven-surefire-event:" + (char) 10 + ":test-error:" + (char) 10 + ":normal-run:"
-                    + (char) 5 + ":UTF-8:" );
-
-        result = encodeMessage( BOOTERCODE_TEST_ERROR, NORMAL_RUN, "msg" );
-        assertThat( toString( result ) )
-                .isEqualTo( ":maven-surefire-event:" + (char) 10 + ":test-error:" + (char) 10 + ":normal-run:"
-                    + (char) 5 + ":UTF-8:\u0000\u0000\u0000\u0003:msg:" );
-
         Channel channel = new Channel();
-        new LegacyMasterProcessChannelEncoder( channel ).stdOut( "msg", false );
+        new EventChannelEncoder( channel ).stdOut( "msg", false );
         assertThat( toString( channel.src ) )
                 .isEqualTo( ":maven-surefire-event:" + (char) 14 + ":std-out-stream:" + (char) 10 + ":normal-run:"
                     + (char) 5 + ":UTF-8:\u0000\u0000\u0000\u0003:msg:" );
 
         channel = new Channel();
-        new LegacyMasterProcessChannelEncoder( channel ).stdErr( null, false );
+        new EventChannelEncoder( channel ).stdErr( null, false );
         assertThat( toString( channel.src ) )
                 .isEqualTo( ":maven-surefire-event:" + (char) 14 + ":std-err-stream:" + (char) 10 + ":normal-run:"
                     + (char) 5 + ":UTF-8:\u0000\u0000\u0000\u0001:\u0000:" );
+
+        ByteBuffer result = new EventChannelEncoder( new Channel() )
+            .encodeMessage( BOOTERCODE_TEST_ERROR, NORMAL_RUN, "msg" );
+        assertThat( toString( result ) )
+            .isEqualTo( ":maven-surefire-event:" + (char) 10 + ":test-error:" + (char) 10 + ":normal-run:"
+                + (char) 5 + ":UTF-8:\u0000\u0000\u0000\u0003:msg:" );
     }
 
     @Test
     public void testConsoleInfo()
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
         encoder.consoleInfoLog( "msg" );
 
@@ -1055,7 +928,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testConsoleError()
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
         encoder.consoleErrorLog( "msg" );
 
@@ -1074,7 +947,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testConsoleErrorLog1() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
         Exception e = new Exception( "msg" );
         encoder.consoleErrorLog( e );
@@ -1112,7 +985,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testConsoleErrorLog2() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
         Exception e = new Exception( "msg" );
         encoder.consoleErrorLog( "msg2", e );
@@ -1150,7 +1023,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testConsoleErrorLog3()
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
         StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
         when( stackTraceWriter.getThrowable() ).thenReturn( new SafeThrowable( "1" ) );
@@ -1168,7 +1041,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testConsoleDebug()
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
         encoder.consoleDebugLog( "msg" );
 
@@ -1184,7 +1057,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testConsoleWarning()
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
 
         encoder.consoleWarningLog( "msg" );
 
@@ -1201,7 +1074,7 @@ public class LegacyMasterProcessChannelEncoderTest
     {
         Stream out = Stream.newStream();
         WritableBufferedByteChannel channel = newBufferedChannel( out );
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( channel );
+        EventChannelEncoder encoder = new EventChannelEncoder( channel );
 
         encoder.stdOut( "msg", false );
         channel.close();
@@ -1218,7 +1091,7 @@ public class LegacyMasterProcessChannelEncoderTest
     {
         Stream out = Stream.newStream();
         WritableBufferedByteChannel channel = newBufferedChannel( out );
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( channel );
+        EventChannelEncoder encoder = new EventChannelEncoder( channel );
 
         encoder.stdOut( "msg", true );
         channel.close();
@@ -1235,7 +1108,7 @@ public class LegacyMasterProcessChannelEncoderTest
     {
         Stream out = Stream.newStream();
         WritableBufferedByteChannel channel = newBufferedChannel( out );
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( channel );
+        EventChannelEncoder encoder = new EventChannelEncoder( channel );
 
         encoder.stdErr( "msg", false );
         channel.close();
@@ -1252,7 +1125,7 @@ public class LegacyMasterProcessChannelEncoderTest
     {
         Stream out = Stream.newStream();
         WritableBufferedByteChannel channel = newBufferedChannel( out );
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( channel );
+        EventChannelEncoder encoder = new EventChannelEncoder( channel );
 
         encoder.stdErr( "msg", true );
         channel.close();
@@ -1270,10 +1143,10 @@ public class LegacyMasterProcessChannelEncoderTest
     {
         Stream stream = Stream.newStream();
         WritableBufferedByteChannel channel = newBufferedChannel( stream );
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( channel );
+        EventChannelEncoder encoder = new EventChannelEncoder( channel );
 
         Map<String, String> sysProps = ObjectUtils.systemProps();
-        encoder.sendSystemProperties( sysProps );
+        encoder.systemProperties( sysProps );
         channel.close();
 
         for ( Entry<String, String> entry : sysProps.entrySet() )
@@ -1304,7 +1177,7 @@ public class LegacyMasterProcessChannelEncoderTest
     {
         Stream out = Stream.newStream();
 
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
         StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
         when( stackTraceWriter.getThrowable() ).thenReturn( new SafeThrowable( "1" ) );
         when( stackTraceWriter.smartTrimmedStackTrace() ).thenReturn( "2" );
@@ -1321,7 +1194,7 @@ public class LegacyMasterProcessChannelEncoderTest
     {
         Stream out = Stream.newStream();
 
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
+        EventChannelEncoder encoder = new EventChannelEncoder( newBufferedChannel( out ) );
         StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
         when( stackTraceWriter.getThrowable() ).thenReturn( new SafeThrowable( "1" ) );
         when( stackTraceWriter.smartTrimmedStackTrace() ).thenReturn( "2" );
@@ -1338,7 +1211,7 @@ public class LegacyMasterProcessChannelEncoderTest
     {
         Stream out = Stream.newStream();
         WritableBufferedByteChannel channel = newBufferedChannel( out );
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( channel );
+        EventChannelEncoder encoder = new EventChannelEncoder( channel );
 
         Thread.currentThread().interrupt();
         try
@@ -1373,11 +1246,6 @@ public class LegacyMasterProcessChannelEncoderTest
             return out.toByteArray();
         }
 
-        LineNumberReader newReader( Charset streamCharset )
-        {
-            return new LineNumberReader( new StringReader( new String( toByteArray(), streamCharset ) ) );
-        }
-
         static Stream newStream()
         {
             return new Stream( new ByteArrayOutputStream() );
@@ -1412,7 +1280,7 @@ public class LegacyMasterProcessChannelEncoderTest
         ByteBuffer src;
 
         @Override
... 376 lines suppressed ...


[maven-surefire] 02/03: [jenkins] temporarily removed JDK 16EA, see INFRA-20981

Posted by ti...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 10d7e33d6a20d8cfa811c220a966821b4f972c69
Author: tibordigana <ti...@gmail.com>
AuthorDate: Sat Jan 16 03:11:36 2021 +0100

    [jenkins] temporarily removed JDK 16EA, see INFRA-20981
---
 Jenkinsfile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Jenkinsfile b/Jenkinsfile
index c3d1d8b..c6c70da 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -33,7 +33,7 @@ properties(
 final def oses = ['linux':'ubuntu && !H48', 'windows':'Windows']
 final def mavens = env.BRANCH_NAME == 'master' ? ['3.6.x', '3.2.x'] : ['3.6.x']
 // all non-EOL versions and the first EA
-final def jdks = [16, 15, 11, 8, 7]
+final def jdks = [15, 11, 8, 7]
 
 final def options = ['-e', '-V', '-B', '-nsu', '-P', 'run-its']
 final def goals = ['clean', 'install']


[maven-surefire] 03/03: tests should not fail on concurrent access of src/test/java/.../*.ConsoleLoggerMock.java

Posted by ti...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 8f9745e6d544ba6221e1a3f994483e4372b99075
Author: tibordigana <ti...@gmail.com>
AuthorDate: Sat Jan 16 12:05:25 2021 +0100

    tests should not fail on concurrent access of src/test/java/.../*.ConsoleLoggerMock.java
---
 .../booterclient/output/ForkClientTest.java        | 28 +++++++++++-----------
 .../extensions/ForkedProcessEventNotifierTest.java | 28 +++++++++++-----------
 .../booter/spi/CommandChannelDecoderTest.java      | 28 +++++++++++-----------
 3 files changed, 42 insertions(+), 42 deletions(-)

diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClientTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClientTest.java
index 531bafb..a0b29cd 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClientTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClientTest.java
@@ -1910,9 +1910,9 @@ public class ForkClientTest
         final boolean isInfo;
         final boolean isWarning;
         final boolean isError;
-        boolean called;
-        boolean isDebugEnabledCalled;
-        boolean isInfoEnabledCalled;
+        private volatile boolean called;
+        private volatile boolean isDebugEnabledCalled;
+        private volatile boolean isInfoEnabledCalled;
 
         ConsoleLoggerMock( boolean isDebug, boolean isInfo, boolean isWarning, boolean isError )
         {
@@ -1923,7 +1923,7 @@ public class ForkClientTest
         }
 
         @Override
-        public boolean isDebugEnabled()
+        public synchronized boolean isDebugEnabled()
         {
             isDebugEnabledCalled = true;
             called = true;
@@ -1931,14 +1931,14 @@ public class ForkClientTest
         }
 
         @Override
-        public void debug( String message )
+        public synchronized void debug( String message )
         {
             debug.add( message );
             called = true;
         }
 
         @Override
-        public boolean isInfoEnabled()
+        public synchronized boolean isInfoEnabled()
         {
             isInfoEnabledCalled = true;
             called = true;
@@ -1946,52 +1946,52 @@ public class ForkClientTest
         }
 
         @Override
-        public void info( String message )
+        public synchronized void info( String message )
         {
             info.add( message );
             called = true;
         }
 
         @Override
-        public boolean isWarnEnabled()
+        public synchronized boolean isWarnEnabled()
         {
             called = true;
             return isWarning;
         }
 
         @Override
-        public void warning( String message )
+        public synchronized void warning( String message )
         {
             called = true;
         }
 
         @Override
-        public boolean isErrorEnabled()
+        public synchronized boolean isErrorEnabled()
         {
             called = true;
             return isError;
         }
 
         @Override
-        public void error( String message )
+        public synchronized void error( String message )
         {
             error.add( message );
             called = true;
         }
 
         @Override
-        public void error( String message, Throwable t )
+        public synchronized void error( String message, Throwable t )
         {
             called = true;
         }
 
         @Override
-        public void error( Throwable t )
+        public synchronized void error( Throwable t )
         {
             called = true;
         }
 
-        boolean isCalled()
+        synchronized boolean isCalled()
         {
             return called;
         }
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/ForkedProcessEventNotifierTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/ForkedProcessEventNotifierTest.java
index 558c021..abbdeb0 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/ForkedProcessEventNotifierTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/ForkedProcessEventNotifierTest.java
@@ -1214,9 +1214,9 @@ public class ForkedProcessEventNotifierTest
         final boolean isInfo;
         final boolean isWarning;
         final boolean isError;
-        boolean called;
-        boolean isDebugEnabledCalled;
-        boolean isInfoEnabledCalled;
+        private volatile boolean called;
+        private volatile boolean isDebugEnabledCalled;
+        private volatile boolean isInfoEnabledCalled;
 
         ConsoleLoggerMock( boolean isDebug, boolean isInfo, boolean isWarning, boolean isError )
         {
@@ -1227,7 +1227,7 @@ public class ForkedProcessEventNotifierTest
         }
 
         @Override
-        public boolean isDebugEnabled()
+        public synchronized boolean isDebugEnabled()
         {
             isDebugEnabledCalled = true;
             called = true;
@@ -1235,14 +1235,14 @@ public class ForkedProcessEventNotifierTest
         }
 
         @Override
-        public void debug( String message )
+        public synchronized void debug( String message )
         {
             debug.add( message );
             called = true;
         }
 
         @Override
-        public boolean isInfoEnabled()
+        public synchronized boolean isInfoEnabled()
         {
             isInfoEnabledCalled = true;
             called = true;
@@ -1250,52 +1250,52 @@ public class ForkedProcessEventNotifierTest
         }
 
         @Override
-        public void info( String message )
+        public synchronized void info( String message )
         {
             info.add( message );
             called = true;
         }
 
         @Override
-        public boolean isWarnEnabled()
+        public synchronized boolean isWarnEnabled()
         {
             called = true;
             return isWarning;
         }
 
         @Override
-        public void warning( String message )
+        public synchronized void warning( String message )
         {
             called = true;
         }
 
         @Override
-        public boolean isErrorEnabled()
+        public synchronized boolean isErrorEnabled()
         {
             called = true;
             return isError;
         }
 
         @Override
-        public void error( String message )
+        public synchronized void error( String message )
         {
             error.add( message );
             called = true;
         }
 
         @Override
-        public void error( String message, Throwable t )
+        public synchronized void error( String message, Throwable t )
         {
             called = true;
         }
 
         @Override
-        public void error( Throwable t )
+        public synchronized void error( Throwable t )
         {
             called = true;
         }
 
-        boolean isCalled()
+        synchronized boolean isCalled()
         {
             return called;
         }
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/CommandChannelDecoderTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/CommandChannelDecoderTest.java
index f31d9d8..8ac1829 100644
--- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/CommandChannelDecoderTest.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/CommandChannelDecoderTest.java
@@ -430,9 +430,9 @@ public class CommandChannelDecoderTest
         final boolean isInfo;
         final boolean isWarning;
         final boolean isError;
-        boolean called;
-        boolean isDebugEnabledCalled;
-        boolean isInfoEnabledCalled;
+        private volatile boolean called;
+        private volatile boolean isDebugEnabledCalled;
+        private volatile boolean isInfoEnabledCalled;
 
         ConsoleLoggerMock( boolean isDebug, boolean isInfo, boolean isWarning, boolean isError )
         {
@@ -443,7 +443,7 @@ public class CommandChannelDecoderTest
         }
 
         @Override
-        public boolean isDebugEnabled()
+        public synchronized boolean isDebugEnabled()
         {
             isDebugEnabledCalled = true;
             called = true;
@@ -451,14 +451,14 @@ public class CommandChannelDecoderTest
         }
 
         @Override
-        public void debug( String message )
+        public synchronized void debug( String message )
         {
             debug.add( message );
             called = true;
         }
 
         @Override
-        public boolean isInfoEnabled()
+        public synchronized boolean isInfoEnabled()
         {
             isInfoEnabledCalled = true;
             called = true;
@@ -466,52 +466,52 @@ public class CommandChannelDecoderTest
         }
 
         @Override
-        public void info( String message )
+        public synchronized void info( String message )
         {
             info.add( message );
             called = true;
         }
 
         @Override
-        public boolean isWarnEnabled()
+        public synchronized boolean isWarnEnabled()
         {
             called = true;
             return isWarning;
         }
 
         @Override
-        public void warning( String message )
+        public synchronized void warning( String message )
         {
             called = true;
         }
 
         @Override
-        public boolean isErrorEnabled()
+        public synchronized boolean isErrorEnabled()
         {
             called = true;
             return isError;
         }
 
         @Override
-        public void error( String message )
+        public synchronized void error( String message )
         {
             error.add( message );
             called = true;
         }
 
         @Override
-        public void error( String message, Throwable t )
+        public synchronized void error( String message, Throwable t )
         {
             called = true;
         }
 
         @Override
-        public void error( Throwable t )
+        public synchronized void error( Throwable t )
         {
             called = true;
         }
 
-        boolean isCalled()
+        synchronized boolean isCalled()
         {
             return called;
         }