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 2020/09/27 07:55:42 UTC

[maven-surefire] branch master updated: [SUREFIRE-1846] Remove Base64 in the Encoder/Decoder and gain the performance for the communication flow: Fork to Plugin

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


The following commit(s) were added to refs/heads/master by this push:
     new c5319bf  [SUREFIRE-1846] Remove Base64 in the Encoder/Decoder and gain the performance for the communication flow: Fork to Plugin
c5319bf is described below

commit c5319bf493c2929d13cd62d89c34704d5d5cb33a
Author: tibordigana <ti...@apache.org>
AuthorDate: Sat Aug 1 23:09:34 2020 +0200

    [SUREFIRE-1846] Remove Base64 in the Encoder/Decoder and gain the performance for the communication flow: Fork to Plugin
---
 .../plugin/surefire/booterclient/ForkStarter.java  |    8 +
 .../surefire/extensions/EventConsumerThread.java   | 1002 ++++++++++----
 .../booterclient/ForkingRunListenerTest.java       |   27 +-
 .../plugin/surefire/booterclient/MainClass.java    |    2 +-
 .../booterclient/output/ForkClientTest.java        |   33 +-
 .../output/ThreadedStreamConsumerTest.java         |    2 +-
 .../maven/plugin/surefire/extensions/E2ETest.java  |  133 +-
 .../extensions/EventConsumerThreadTest.java        | 1237 +++++++++++++++++
 .../extensions/ForkedProcessEventNotifierTest.java |  156 +--
 .../org/apache/maven/surefire/JUnit4SuiteTest.java |    2 +
 .../maven/surefire/extensions/ForkChannelTest.java |   18 +-
 .../maven/surefire/api/booter/Constants.java       |   51 +-
 .../api/booter/ForkedProcessEventType.java         |  260 +++-
 .../apache/maven/surefire/api/report/RunMode.java  |   28 +-
 .../spi/LegacyMasterProcessChannelDecoder.java     |    2 +-
 .../spi/LegacyMasterProcessChannelEncoder.java     |  357 +++--
 .../spi/LegacyMasterProcessChannelEncoderTest.java | 1412 ++++++++++++--------
 .../surefire/extensions/ForkNodeArguments.java     |    3 +
 .../jiras/Surefire224WellFormedXmlFailuresIT.java  |    2 +-
 .../java/wellFormedXmlFailures/TestSurefire3.java  |    2 +-
 20 files changed, 3547 insertions(+), 1190 deletions(-)

diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java
index 0a333f6..1ac3917 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
@@ -912,6 +912,14 @@ public class ForkStarter
             return InPluginProcessDumpSingleton.getSingleton().dumpStreamText( text, dumpLogDir, forkChannelId );
         }
 
+        @Nonnull
+        @Override
+        public File dumpStreamException( @Nonnull Throwable t )
+        {
+            return InPluginProcessDumpSingleton.getSingleton()
+                .dumpStreamException( t, t.getLocalizedMessage(), dumpLogDir, forkChannelId );
+        }
+
         @Override
         public void logWarningAtEnd( @Nonnull String text )
         {
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 1d5686b..2647b60 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
@@ -44,30 +44,41 @@ 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.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.api.report.RunMode;
-import org.apache.maven.surefire.api.report.StackTraceWriter;
-import org.apache.maven.surefire.api.report.TestSetReportEntry;
-import org.apache.maven.surefire.shared.codec.binary.Base64;
 
+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.Iterator;
+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.surefire.api.booter.ForkedProcessEventType.MAGIC_NUMBER;
+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;
-import static org.apache.maven.surefire.api.report.RunMode.MODES;
 
 /**
  *
@@ -82,8 +93,64 @@ public class EventConsumerThread extends CloseableDaemonThread
             "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 Base64 BASE64 = new Base64();
+
+    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;
@@ -133,371 +200,778 @@ public class EventConsumerThread extends CloseableDaemonThread
     @SuppressWarnings( "checkstyle:innerassignment" )
     private void decode() throws IOException
     {
-        List<String> tokens = new ArrayList<>();
-        StringBuilder line = new StringBuilder();
-        StringBuilder token = new StringBuilder( MAGIC_NUMBER.length() );
-        ByteBuffer buffer = ByteBuffer.allocate( 1024 );
-        buffer.position( buffer.limit() );
-        boolean streamContinues;
+        Map<Segment, ForkedProcessEventType> eventTypes = mapEventTypes();
+        Map<Segment, RunMode> runModes = mapRunModes();
+        Memento memento = new Memento();
+        memento.bb.limit( 0 );
 
-        start:
         do
         {
-            line.setLength( 0 );
-            tokens.clear();
-            token.setLength( 0 );
-            FrameCompletion completion = null;
-            for ( boolean frameStarted = false; streamContinues = read( buffer ); completion = null )
+            try
             {
-                char c = (char) buffer.get();
-
-                if ( c == '\n' || c == '\r' )
+                ForkedProcessEventType eventType = readEventType( eventTypes, memento );
+                if ( eventType == null )
                 {
-                    printExistingLine( line );
-                    continue start;
+                    throw new MalformedFrameException( memento.line.positionByteBuffer, memento.bb.position() );
                 }
-
-                line.append( c );
-
-                if ( !frameStarted )
+                RunMode runMode = null;
+                for ( SegmentType segmentType : nextSegmentType( eventType ) )
                 {
-                    if ( c == ':' )
+                    if ( segmentType == null )
                     {
-                        frameStarted = true;
-                        token.setLength( 0 );
-                        tokens.clear();
+                        break;
                     }
-                }
-                else
-                {
-                    if ( c == ':' )
+
+                    switch ( segmentType )
                     {
-                        tokens.add( token.toString() );
-                        token.setLength( 0 );
-                        completion = frameCompleteness( tokens );
-                        if ( completion == FrameCompletion.COMPLETE )
-                        {
-                            line.setLength( 0 );
+                        case RUN_MODE:
+                            runMode = runModes.get( readSegment( memento ) );
                             break;
-                        }
-                        else if ( completion == FrameCompletion.MALFORMED )
-                        {
-                            printExistingLine( line );
-                            continue start;
-                        }
-                    }
-                    else
-                    {
-                        token.append( c );
+                        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 );
                     }
                 }
             }
-
-            if ( completion == FrameCompletion.COMPLETE )
+            catch ( MalformedFrameException e )
             {
-                Event event = toEvent( tokens );
-                if ( !disabled && event != null )
+                if ( e.hasValidPositions() )
                 {
-                    eventHandler.handleEvent( event );
+                    int length = e.readTo - e.readFrom;
+                    memento.line.write( memento.bb, e.readFrom, length );
                 }
             }
-
-            if ( !streamContinues )
+            catch ( RuntimeException e )
             {
-                printExistingLine( line );
-                return;
+                arguments.dumpStreamException( e );
+            }
+            catch ( IOException e )
+            {
+                printRemainingStream( memento );
+                throw e;
+            }
+            finally
+            {
+                memento.reset();
             }
         }
         while ( true );
     }
 
-    private boolean read( ByteBuffer buffer ) throws IOException
+    protected ForkedProcessEventType readEventType( Map<Segment, ForkedProcessEventType> eventTypes, Memento memento )
+        throws IOException, MalformedFrameException
     {
-        if ( buffer.hasRemaining() && buffer.position() > 0 )
+        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 )
         {
-            return true;
+            string = "";
+        }
+        else if ( readCount == 1 )
+        {
+            read( memento, 1 );
+            byte oneChar = memento.bb.get();
+            string = oneChar == 0 ? null : String.valueOf( (char) oneChar );
         }
         else
         {
-            buffer.clear();
-            boolean isEndOfStream = channel.read( buffer ) == -1;
-            buffer.flip();
-            return !isEndOfStream;
+            string = readString( memento, readCount );
         }
+
+        checkDelimiter( memento );
+        return string;
     }
 
-    private void printExistingLine( StringBuilder line )
+    @Nonnull
+    @SuppressWarnings( "checkstyle:magicnumber" )
+    protected Segment readSegment( Memento memento ) throws IOException, MalformedFrameException
     {
-        if ( line.length() != 0 )
+        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 )
         {
-            ConsoleLogger logger = arguments.getConsoleLogger();
-            String s = line.toString().trim();
-            if ( s.contains( PRINTABLE_JVM_NATIVE_STREAM ) )
+            isDefaultEncoding = true;
+            for ( int i = 0; i < length; i++ )
             {
-                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 );
-                }
+                isDefaultEncoding &= DEFAULT_STREAM_ENCODING_BYTES[i] == array[offset + i];
             }
-            else
-            {
-                if ( isJvmError( s ) )
-                {
-                    logger.error( 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() );
+        }
 
-                if ( logger.isDebugEnabled() )
+        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] )
                 {
-                    logger.debug( s );
+                    throw new MalformedFrameException( memento.line.positionByteBuffer, bb.position() + shift );
                 }
             }
         }
+        finally
+        {
+            bb.position( bb.position() + shift );
+        }
+
+        checkDelimiter( memento );
     }
 
-    private Event toEvent( List<String> tokensInFrame )
+    @SuppressWarnings( "checkstyle:magicnumber" )
+    private static void checkDelimiter( Memento memento ) throws MalformedFrameException
     {
-        Iterator<String> tokens = tokensInFrame.iterator();
-        String header = tokens.next();
-        assert header != null;
-
-        ForkedProcessEventType event = ForkedProcessEventType.byOpcode( tokens.next() );
+        ByteBuffer bb = memento.bb;
+        if ( ( 0xff & bb.get() ) != ':' )
+        {
+            throw new MalformedFrameException( memento.line.positionByteBuffer, bb.position() );
+        }
+    }
 
-        if ( event == null )
+    static SegmentType[] nextSegmentType( ForkedProcessEventType eventType )
+    {
+        switch ( eventType )
         {
-            return null;
+            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 );
         }
+    }
 
-        if ( event.isControlCategory() )
+    protected StreamReadStatus read( Memento memento, int recommendedCount ) throws IOException
+    {
+        ByteBuffer buffer = memento.bb;
+        if ( buffer.remaining() >= recommendedCount && buffer.position() != 0 )
+        {
+            return OVERFLOW;
+        }
+        else
         {
-            switch ( event )
+            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
             {
-                case BOOTERCODE_BYE:
-                    return new ControlByeEvent();
-                case BOOTERCODE_STOP_ON_NEXT_TEST:
-                    return new ControlStopOnNextTestEvent();
-                case BOOTERCODE_NEXT_TEST:
-                    return new ControlNextTestEvent();
-                default:
-                    throw new IllegalStateException( "Unknown enum " + event );
+                return readBytes >= recommendedCount ? OVERFLOW : UNDERFLOW;
             }
         }
-        else if ( event.isConsoleErrorCategory() || event.isJvmExitError() )
+    }
+
+    static Event toEvent( ForkedProcessEventType eventType, RunMode runMode, List<Object> args )
+    {
+        switch ( eventType )
         {
-            Charset encoding = Charset.forName( tokens.next() );
-            StackTraceWriter stackTraceWriter = decodeTrace( encoding, tokens.next(), tokens.next(), tokens.next() );
-            return event.isConsoleErrorCategory()
-                ? new ConsoleErrorEvent( stackTraceWriter )
-                : new JvmExitErrorEvent( stackTraceWriter );
+            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 );
         }
-        else if ( event.isConsoleCategory() )
+    }
+
+    private static void printCorruptedStream( Memento memento )
+    {
+        ByteBuffer bb = memento.bb;
+        if ( bb.hasRemaining() )
         {
-            Charset encoding = Charset.forName( tokens.next() );
-            String msg = decode( tokens.next(), encoding );
-            switch ( event )
-            {
-                case BOOTERCODE_CONSOLE_INFO:
-                    return new ConsoleInfoEvent( msg );
-                case BOOTERCODE_CONSOLE_DEBUG:
-                    return new ConsoleDebugEvent( msg );
-                case BOOTERCODE_CONSOLE_WARNING:
-                    return new ConsoleWarningEvent( msg );
-                default:
-                    throw new IllegalStateException( "Unknown enum " + event );
-            }
+            int bytesToWrite = bb.remaining();
+            memento.line.write( bb, bb.position(), bytesToWrite );
+            bb.position( bb.position() + bytesToWrite );
         }
-        else if ( event.isStandardStreamCategory() )
+    }
+
+    /**
+     * 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 )
         {
-            RunMode mode = MODES.get( tokens.next() );
-            Charset encoding = Charset.forName( tokens.next() );
-            String output = decode( tokens.next(), encoding );
-            switch ( event )
+            if ( lineLower.contains( errorPattern ) )
             {
-                case BOOTERCODE_STDOUT:
-                    return new StandardStreamOutEvent( mode, output );
-                case BOOTERCODE_STDOUT_NEW_LINE:
-                    return new StandardStreamOutWithNewLineEvent( mode, output );
-                case BOOTERCODE_STDERR:
-                    return new StandardStreamErrEvent( mode, output );
-                case BOOTERCODE_STDERR_NEW_LINE:
-                    return new StandardStreamErrWithNewLineEvent( mode, output );
-                default:
-                    throw new IllegalStateException( "Unknown enum " + event );
+                return true;
             }
         }
-        else if ( event.isSysPropCategory() )
+        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() )
         {
-            RunMode mode = MODES.get( tokens.next() );
-            Charset encoding = Charset.forName( tokens.next() );
-            String key = decode( tokens.next(), encoding );
-            String value = decode( tokens.next(), encoding );
-            return new SystemPropertyEvent( mode, key, value );
+            throw new MalformedFrameException( errorStreamFrom, input.position() );
         }
-        else if ( event.isTestCategory() )
+        
+        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; )
         {
-            RunMode mode = MODES.get( tokens.next() );
-            Charset encoding = Charset.forName( tokens.next() );
-            TestSetReportEntry reportEntry =
-                decodeReportEntry( encoding, tokens.next(), tokens.next(), tokens.next(), tokens.next(),
-                    tokens.next(), tokens.next(), tokens.next(), tokens.next(), tokens.next(), tokens.next() );
+            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;
+            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() );
 
-            switch ( event )
+            if ( isLastChunk || !output.hasRemaining() )
             {
-                case BOOTERCODE_TESTSET_STARTING:
-                    return new TestsetStartingEvent( mode, reportEntry );
-                case BOOTERCODE_TESTSET_COMPLETED:
-                    return new TestsetCompletedEvent( mode, reportEntry );
-                case BOOTERCODE_TEST_STARTING:
-                    return new TestStartingEvent( mode, reportEntry );
-                case BOOTERCODE_TEST_SUCCEEDED:
-                    return new TestSucceededEvent( mode, reportEntry );
-                case BOOTERCODE_TEST_FAILED:
-                    return new TestFailedEvent( mode, reportEntry );
-                case BOOTERCODE_TEST_SKIPPED:
-                    return new TestSkippedEvent( mode, reportEntry );
-                case BOOTERCODE_TEST_ERROR:
-                    return new TestErrorEvent( mode, reportEntry );
-                case BOOTERCODE_TEST_ASSUMPTIONFAILURE:
-                    return new TestAssumptionFailureEvent( mode, reportEntry );
-                default:
-                    throw new IllegalStateException( "Unknown enum " + event );
+                strings.add( output.flip().toString() );
+                output.clear();
             }
         }
 
-        throw new IllegalStateException( "Missing a branch for the event type " + event );
+        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
     }
 
-    private static FrameCompletion frameCompleteness( List<String> tokens )
+    /**
+     * This class avoids locking which gains the performance of this decoder.
+     */
+    private class BufferedStream
     {
-        if ( !tokens.isEmpty() && !MAGIC_NUMBER.equals( tokens.get( 0 ) ) )
+        private byte[] buffer;
+        private int count;
+        private int positionByteBuffer;
+        private boolean isNewLine;
+
+        BufferedStream( int capacity )
         {
-            return FrameCompletion.MALFORMED;
+            this.buffer = new byte[capacity];
         }
 
-        if ( tokens.size() >= 2 )
+        void write( ByteBuffer bb, int position, int length )
         {
-            String opcode = tokens.get( 1 );
-            ForkedProcessEventType event = ForkedProcessEventType.byOpcode( opcode );
-            if ( event == null )
+            ensureCapacity( length );
+            byte[] array = bb.array();
+            int pos = bb.arrayOffset() + position;
+            while ( length-- > 0 )
             {
-                return FrameCompletion.MALFORMED;
+                positionByteBuffer++;
+                byte b = array[pos++];
+                if ( b == '\r' || b == '\n' )
+                {
+                    if ( !isNewLine )
+                    {
+                        printExistingLine();
+                        count = 0;
+                    }
+                    isNewLine = true;
+                }
+                else
+                {
+                    buffer[count++] = b;
+                    isNewLine = false;
+                }
             }
-            else if ( event.isControlCategory() )
+        }
+
+        private boolean isEmpty()
+        {
+            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 )
             {
-                return FrameCompletion.COMPLETE;
+                throw new OutOfMemoryError();
             }
-            else if ( event.isConsoleErrorCategory() )
+
+            if ( oldCapacity < exactCapacity )
             {
-                return tokens.size() == 6 ? FrameCompletion.COMPLETE : FrameCompletion.NOT_COMPLETE;
+                int newCapacity = oldCapacity << 1;
+                buffer = Arrays.copyOf( buffer, max( newCapacity, exactCapacity ) );
             }
-            else if ( event.isConsoleCategory() )
+        }
+
+        void printExistingLine()
+        {
+            if ( isEmpty() )
             {
-                return tokens.size() == 4 ? FrameCompletion.COMPLETE : FrameCompletion.NOT_COMPLETE;
+                return;
             }
-            else if ( event.isStandardStreamCategory() )
+            ConsoleLogger logger = arguments.getConsoleLogger();
+            String s = toString();
+            if ( s.contains( PRINTABLE_JVM_NATIVE_STREAM ) )
             {
-                return tokens.size() == 5 ? FrameCompletion.COMPLETE : FrameCompletion.NOT_COMPLETE;
+                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 ( event.isSysPropCategory() )
+            else
             {
-                return tokens.size() == 6 ? FrameCompletion.COMPLETE : FrameCompletion.NOT_COMPLETE;
+                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() );
             }
-            else if ( event.isTestCategory() )
+        }
+    }
+
+    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 )
+        {
+            if ( charset.name().equals( defaultDecoder.charset().name() ) )
             {
-                return tokens.size() == 14 ? FrameCompletion.COMPLETE : FrameCompletion.NOT_COMPLETE;
+                currentDecoder = defaultDecoder;
             }
-            else if ( event.isJvmExitError() )
+            else
             {
-                return tokens.size() == 6 ? FrameCompletion.COMPLETE : FrameCompletion.NOT_COMPLETE;
+                currentDecoder = charset.newDecoder()
+                    .onMalformedInput( REPLACE )
+                    .onUnmappableCharacter( REPLACE );
             }
         }
-        return FrameCompletion.NOT_COMPLETE;
     }
 
-    static String decode( String line, Charset encoding )
+    static class Segment
     {
-        // ForkedChannelEncoder is encoding the stream with US_ASCII
-        return line == null || "-".equals( line )
-            ? null
-            : new String( BASE64.decode( line.getBytes( US_ASCII ) ), encoding );
-    }
+        private final byte[] array;
+        private final int fromIndex;
+        private final int length;
+        private final int hashCode;
 
-    private static StackTraceWriter decodeTrace( Charset encoding, String encTraceMessage,
-                                                 String encSmartTrimmedStackTrace, String encStackTrace )
-    {
-        String traceMessage = decode( encTraceMessage, encoding );
-        String stackTrace = decode( encStackTrace, encoding );
-        String smartTrimmedStackTrace = decode( encSmartTrimmedStackTrace, encoding );
-        boolean exists = traceMessage != null || stackTrace != null || smartTrimmedStackTrace != null;
-        return exists ? new DeserializedStacktraceWriter( traceMessage, smartTrimmedStackTrace, stackTrace ) : null;
-    }
+        Segment( byte[] array, int fromIndex, int length )
+        {
+            this.array = array;
+            this.fromIndex = fromIndex;
+            this.length = length;
 
-    static TestSetReportEntry decodeReportEntry( Charset encoding,
-                                                 // ReportEntry:
-                                                 String encSource, String encSourceText, String encName,
-                                                 String encNameText, String encGroup, String encMessage,
-                                                 String encTimeElapsed,
-                                                 // StackTraceWriter:
-                                                 String encTraceMessage,
-                                                 String encSmartTrimmedStackTrace, String encStackTrace )
-        throws NumberFormatException
-    {
-        if ( encoding == null )
+            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()
         {
-            // corrupted or incomplete stream
-            return null;
+            return hashCode;
         }
 
-        String source = decode( encSource, encoding );
-        String sourceText = decode( encSourceText, encoding );
-        String name = decode( encName, encoding );
-        String nameText = decode( encNameText, encoding );
-        String group = decode( encGroup, encoding );
-        StackTraceWriter stackTraceWriter =
-            decodeTrace( encoding, encTraceMessage, encSmartTrimmedStackTrace, encStackTrace );
-        Integer elapsed = decodeToInteger( encTimeElapsed );
-        String message = decode( encMessage, encoding );
-        return reportEntry( source, sourceText, name, nameText,
-            group, stackTraceWriter, elapsed, message, Collections.<String, String>emptyMap() );
-    }
+        @Override
+        public boolean equals( Object obj )
+        {
+            if ( !( obj instanceof Segment ) )
+            {
+                return false;
+            }
 
-    static Integer decodeToInteger( String line )
-    {
-        return line == null || "-".equals( line ) ? null : Integer.decode( line );
-    }
+            Segment that = (Segment) obj;
+            if ( that.length != length )
+            {
+                return false;
+            }
 
-    private static boolean isJvmError( String line )
-    {
-        String lineLower = line.toLowerCase();
-        for ( String errorPattern : JVM_ERROR_PATTERNS )
-        {
-            if ( lineLower.contains( errorPattern ) )
+            for ( int i = 0; i < length; i++ )
             {
-                return true;
+                if ( that.array[that.fromIndex + i] != array[fromIndex + i] )
+                {
+                    return false;
+                }
             }
+            return true;
         }
-        return false;
     }
 
     /**
-     * Determines whether the frame is complete or malformed.
+     *
      */
-    private enum FrameCompletion
+    static class MalformedFrameException extends Exception
     {
-        NOT_COMPLETE,
-        COMPLETE,
-        MALFORMED
+        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;
+        }
     }
 }
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 8d3a3e5..8887e4d 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
@@ -45,6 +45,7 @@ import javax.annotation.Nonnull;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.Closeable;
+import java.io.File;
 import java.io.PrintStream;
 import java.nio.channels.ReadableByteChannel;
 import java.util.ArrayList;
@@ -57,7 +58,13 @@ import java.util.concurrent.TimeUnit;
 
 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;
+import static org.fest.assertions.MapAssert.entry;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 /**
@@ -193,20 +200,27 @@ public class ForkingRunListenerTest
         TestSetMockReporterFactory providerReporterFactory = new TestSetMockReporterFactory();
         ForkClient forkStreamClient = new ForkClient( providerReporterFactory, new MockNotifiableTestStream(), 1 );
 
-        byte[] cmd = ":maven-surefire-event:sys-prop:normal-run:UTF-8:azE=:djE=:\n".getBytes();
+        byte[] cmd = ( ":maven-surefire-event:\u0008:sys-prop:" + (char) 10 + ":normal-run:\u0005:UTF-8:"
+            + "\u0000\u0000\u0000\u0002:k1:\u0000\u0000\u0000\u0002:v1:\n" ).getBytes();
         for ( Event e : streamToEvent( cmd ) )
         {
             forkStreamClient.handleEvent( e );
         }
-        cmd = "\n:maven-surefire-event:sys-prop:normal-run:UTF-8:azI=:djI=:\n".getBytes();
+        cmd = ( "\n:maven-surefire-event:\u0008:sys-prop:" + (char) 10 + ":normal-run:\u0005:UTF-8:"
+            + "\u0000\u0000\u0000\u0002:k2:\u0000\u0000\u0000\u0002:v2:\n" ).getBytes();
         for ( Event e : streamToEvent( cmd ) )
         {
             forkStreamClient.handleEvent( e );
         }
 
-        assertTrue( forkStreamClient.getTestVmSystemProperties().size() == 2 );
-        assertTrue( forkStreamClient.getTestVmSystemProperties().containsKey( "k1" ) );
-        assertTrue( forkStreamClient.getTestVmSystemProperties().containsKey( "k2" ) );
+        assertThat( forkStreamClient.getTestVmSystemProperties() )
+            .hasSize( 2 );
+
+        assertThat( forkStreamClient.getTestVmSystemProperties() )
+            .includes( entry( "k1", "v1" ) );
+
+        assertThat( forkStreamClient.getTestVmSystemProperties() )
+            .includes( entry( "k2", "v2" ) );
     }
 
     public void testMultipleEntries() throws Exception
@@ -286,12 +300,15 @@ public class ForkingRunListenerTest
         CountdownCloseable countdown = new CountdownCloseable( mock( Closeable.class ), 1 );
         ConsoleLogger logger = mock( ConsoleLogger.class );
         ForkNodeArguments arguments = mock( ForkNodeArguments.class );
+        when( arguments.dumpStreamText( anyString() ) ).thenReturn( new File( "" ) );
         when( arguments.getConsoleLogger() ).thenReturn( logger );
         ReadableByteChannel channel = newChannel( new ByteArrayInputStream( stream ) );
         try ( EventConsumerThread t = new EventConsumerThread( "t", channel, handler, countdown, arguments ) )
         {
             t.start();
             countdown.awaitClosed();
+            verifyZeroInteractions( logger );
+            verify( arguments, never() ).dumpStreamText( anyString() );
             for ( int i = 0, size = handler.countEventsInCache(); i < size; i++ )
             {
                 events.add( handler.pullEvent() );
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 dc435b5..3d58ade 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
@@ -34,7 +34,7 @@ public class MainClass
         }
         else
         {
-            System.out.println( ":maven-surefire-event:bye:" );
+            System.out.println( ":maven-surefire-event:\u0003:bye:" );
             String byeAck = ":maven-surefire-command:bye-ack:";
             byte[] cmd = new byte[byeAck.length()];
             int len = System.in.read( cmd );
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 548c3e6..60d48f3 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
@@ -67,8 +67,6 @@ import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
 import static java.nio.channels.Channels.newChannel;
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.apache.maven.surefire.shared.codec.binary.Base64.encodeBase64String;
 import static org.apache.maven.plugin.surefire.booterclient.MockReporter.CONSOLE_DEBUG;
 import static org.apache.maven.plugin.surefire.booterclient.MockReporter.CONSOLE_ERR;
 import static org.apache.maven.plugin.surefire.booterclient.MockReporter.CONSOLE_INFO;
@@ -177,8 +175,6 @@ public class ForkClientTest
         assertThat( eventHandler.sizeOfEventCache() )
             .isEqualTo( 0 );
 
-        verify( logger ).isDebugEnabled();
-
         verifyNoMoreInteractions( logger );
     }
 
@@ -206,8 +202,6 @@ public class ForkClientTest
         assertThat( eventHandler.sizeOfEventCache() )
             .isEqualTo( 0 );
 
-        verify( logger ).isDebugEnabled();
-
         verifyNoMoreInteractions( logger );
     }
 
@@ -235,8 +229,6 @@ public class ForkClientTest
         assertThat( eventHandler.sizeOfEventCache() )
             .isEqualTo( 0 );
 
-        verify( logger ).isDebugEnabled();
-
         verifyNoMoreInteractions( logger );
     }
 
@@ -264,8 +256,6 @@ public class ForkClientTest
         assertThat( eventHandler.sizeOfEventCache() )
             .isEqualTo( 0 );
 
-        verify( logger ).isDebugEnabled();
-
         verifyNoMoreInteractions( logger );
     }
 
@@ -293,8 +283,6 @@ public class ForkClientTest
         assertThat( eventHandler.sizeOfEventCache() )
             .isEqualTo( 0 );
 
-        verify( logger ).isDebugEnabled();
-
         verifyNoMoreInteractions( logger );
     }
 
@@ -322,8 +310,6 @@ public class ForkClientTest
         assertThat( eventHandler.sizeOfEventCache() )
             .isEqualTo( 0 );
 
-        verify( logger ).isDebugEnabled();
-
         verifyNoMoreInteractions( logger );
     }
 
@@ -373,24 +359,22 @@ public class ForkClientTest
     @Test
     public void shouldLogJvmMessageAndProcessEvent() throws Exception
     {
-        String nativeStream = "Listening for transport dt_socket at address: bla\n:maven-surefire-event:bye:\n";
+        String nativeStream = "Listening for transport dt_socket at address: bla\n:maven-surefire-event:\u0003:bye:\n";
         EH eventHandler = new EH();
         CountdownCloseable countdown = new CountdownCloseable( mock( Closeable.class ), 1 );
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-        when( logger.isDebugEnabled() )
-            .thenReturn( false );
-        when( logger.isInfoEnabled() )
-            .thenReturn( true );
         ForkNodeArguments arguments = mock( ForkNodeArguments.class );
+        when( arguments.dumpStreamText( anyString() ) ).thenReturn( new File( "" ) );
+        ConsoleLogger logger = mock( ConsoleLogger.class );
         when( arguments.getConsoleLogger() ).thenReturn( logger );
         when( logger.isDebugEnabled() ).thenReturn( true );
-        when( logger.isInfoEnabled() ).thenReturn( false );
         ReadableByteChannel channel = newChannel( new ByteArrayInputStream( nativeStream.getBytes() ) );
         try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, arguments ) )
         {
             t.start();
 
             Event event = eventHandler.pullEvent();
+            assertThat( event )
+                .isNotNull();
             assertThat( event.isControlCategory() )
                 .isTrue();
             assertThat( event.getEventType() )
@@ -745,10 +729,9 @@ public class ForkClientTest
     @Test
     public void shouldLogConsoleErrorWithStackTrace() throws Exception
     {
-        String nativeStream = ":maven-surefire-event:console-error-log:UTF-8"
-            + ":" + encodeBase64String( "Listening for transport dt_socket at address: 5005".getBytes( UTF_8 ) )
-            + ":" + encodeBase64String( "s1".getBytes( UTF_8 ) )
-            + ":" + encodeBase64String( "s2".getBytes( UTF_8 ) ) + ":";
+        String nativeStream = ":maven-surefire-event:\u0011:console-error-log:\u0005:UTF-8"
+            + ":\u0000\u0000\u0000\u0032:Listening for transport dt_socket at address: 5005"
+            + ":\u0000\u0000\u0000\u0002:s1:\u0000\u0000\u0000\u0002:s2:";
         EH eventHandler = new EH();
         CountdownCloseable countdown = new CountdownCloseable( mock( Closeable.class ), 1 );
         ConsoleLogger logger = mock( ConsoleLogger.class );
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ThreadedStreamConsumerTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ThreadedStreamConsumerTest.java
index a859c76..3c1fd37 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ThreadedStreamConsumerTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ThreadedStreamConsumerTest.java
@@ -123,7 +123,7 @@ public class ThreadedStreamConsumerTest
     }
 
     @Test
-    public void test3() throws Exception
+    public void testBasicStatus() throws Exception
     {
         final QueueSynchronizer<String> sync = new QueueSynchronizer<>( 2, null );
         sync.pushNext( "1" );
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 34946ef..c420644 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
@@ -19,9 +19,12 @@ package org.apache.maven.plugin.surefire.extensions;
  * under the License.
  */
 
+import org.apache.maven.plugin.surefire.booterclient.output.ThreadedStreamConsumer;
 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.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.api.event.Event;
+import org.apache.maven.surefire.api.report.ConsoleOutputCapture;
 import org.apache.maven.surefire.api.report.ConsoleOutputReceiver;
 import org.apache.maven.surefire.booter.spi.SurefireMasterProcessChannelProcessorFactory;
 import org.apache.maven.surefire.extensions.EventHandler;
@@ -33,6 +36,8 @@ import org.junit.rules.ExpectedException;
 
 import javax.annotation.Nonnull;
 import java.io.Closeable;
+import java.io.File;
+import java.io.PrintStream;
 import java.net.URI;
 import java.nio.ByteBuffer;
 import java.nio.channels.ReadableByteChannel;
@@ -46,7 +51,6 @@ import java.util.concurrent.atomic.AtomicLong;
 
 import static org.fest.assertions.Assertions.assertThat;
 import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -65,11 +69,7 @@ public class E2ETest
     @Test
     public void endToEndTest() throws Exception
     {
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-        ForkNodeArguments arguments = mock( ForkNodeArguments.class );
-        when( arguments.getForkChannelId() ).thenReturn( 1 );
-        when( arguments.getConsoleLogger() ).thenReturn( logger );
-        when( arguments.getSessionId() ).thenReturn( UUID.randomUUID().toString() );
+        ForkNodeArguments arguments = new Arguments( UUID.randomUUID().toString(), 1, new NullConsoleLogger() );
         final SurefireForkChannel server = new SurefireForkChannel( arguments );
 
         final String connection = server.getForkNodeConnectionString();
@@ -84,6 +84,8 @@ public class E2ETest
 
         final CountDownLatch awaitHandlerFinished = new CountDownLatch( 2 );
 
+        final int totalCalls = 400_000; // 400_000; // 1_000_000; // 10_000_000;
+
         Thread t = new Thread()
         {
             @Override
@@ -98,23 +100,23 @@ public class E2ETest
                     }
                 };
 
-                //PrintStream out = System.out;
-                //PrintStream err = System.err;
+                PrintStream out = System.out;
+                PrintStream err = System.err;
 
-                //ConsoleOutputCapture.startCapture( target );
+                ConsoleOutputCapture.startCapture( target );
 
                 try
                 {
                     long t1 = System.currentTimeMillis();
-                    for ( int i = 0; i < 400_000; i++ )
+                    for ( int i = 0; i < totalCalls; i++ )
                     {
-                        //System.out.println( LONG_STRING );
-                        encoder.stdOut( LONG_STRING, true );
+                        System.out.println( LONG_STRING );
+                        //encoder.stdOut( LONG_STRING, true );
                     }
                     long t2 = System.currentTimeMillis();
                     long spent = t2 - t1;
-                    //System.setOut( out );
-                    //System.setErr( err );
+                    System.setOut( out );
+                    System.setErr( err );
                     System.out.println( spent + "ms on write" );
                     awaitHandlerFinished.countDown();
                 }
@@ -149,12 +151,12 @@ public class E2ETest
                     long t2 = System.currentTimeMillis();
                     long spent = t2 - t1;
 
-                    if ( counter.get() % 100_000 == 0 )
+                    if ( counter.get() % 500_000 == 0 )
                     {
                         System.out.println( spent + "ms: " + counter.get() );
                     }
 
-                    if ( counter.get() == 320_000 )
+                    if ( counter.get() == totalCalls - 64 * 1024 )
                     {
                         readTime.set( spent );
                         System.out.println( spent + "ms on read" );
@@ -168,17 +170,9 @@ public class E2ETest
             }
         };
 
-        Closeable c = new Closeable()
-        {
-            @Override
-            public void close()
-            {
-            }
-        };
+        ThreadedStreamConsumer queue = new ThreadedStreamConsumer( h );
 
-        ReadableByteChannel stdOut = mock( ReadableByteChannel.class );
-        when( stdOut.read( any( ByteBuffer.class ) ) ).thenReturn( -1 );
-        server.bindEventHandler( h, new CountdownCloseable( c, 1 ), stdOut )
+        server.bindEventHandler( queue, new CountdownCloseable( new DummyCloseable(), 1 ), new DummyReadableChannel() )
             .start();
 
         assertThat( awaitHandlerFinished.await( 30L, TimeUnit.SECONDS ) )
@@ -186,11 +180,11 @@ public class E2ETest
 
         factory.close();
         server.close();
+        queue.close();
 
-        // 2 seconds while using the encoder/decoder
-        // 160 millis of sending pure data without encoder/decoder
+        // 1.0 seconds while using the encoder/decoder
         assertThat( readTime.get() )
-            .describedAs( "The performance test should assert 2s of read time. "
+            .describedAs( "The performance test should assert 1.0s of read time. "
                 + "The limit 6s guarantees that the read time does not exceed this limit on overloaded CPU." )
             .isPositive()
             .isLessThanOrEqualTo( 6_000L );
@@ -261,4 +255,85 @@ public class E2ETest
             fail( task.get() );
         }
     }
+
+    private static class DummyReadableChannel implements ReadableByteChannel
+    {
+        @Override
+        public int read( ByteBuffer dst )
+        {
+            return 0;
+        }
+
+        @Override
+        public boolean isOpen()
+        {
+            return false;
+        }
+
+        @Override
+        public void close()
+        {
+        }
+    }
+
+    private static class DummyCloseable implements Closeable
+    {
+        @Override
+        public void close()
+        {
+        }
+    }
+
+    private static class Arguments implements ForkNodeArguments
+    {
+        private final String sessionId;
+        private final int id;
+        private final ConsoleLogger logger;
+
+        private Arguments( String sessionId, int id, ConsoleLogger logger )
+        {
+            this.sessionId = sessionId;
+            this.id = id;
+            this.logger = logger;
+        }
+
+        @Nonnull
+        @Override
+        public String getSessionId()
+        {
+            return sessionId;
+        }
+
+        @Override
+        public int getForkChannelId()
+        {
+            return id;
+        }
+
+        @Nonnull
+        @Override
+        public File dumpStreamText( @Nonnull String text )
+        {
+            return new File( "" );
+        }
+
+        @Nonnull
+        @Override
+        public File dumpStreamException( @Nonnull Throwable t )
+        {
+            return new File( "" );
+        }
+
+        @Override
+        public void logWarningAtEnd( @Nonnull String text )
+        {
+        }
+
+        @Nonnull
+        @Override
+        public ConsoleLogger getConsoleLogger()
+        {
+            return logger;
+        }
+    }
 }
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
new file mode 100644
index 0000000..63287df
--- /dev/null
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThreadTest.java
@@ -0,0 +1,1237 @@
+package org.apache.maven.plugin.surefire.extensions;
+
+/*
+ * 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.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.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 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 );
+    }
+
+    @Test( timeout = 60_000L )
+    public void performanceTest() throws Exception
+    {
+        final long[] staredAt = {0};
+        final long[] finishedAt = {0};
+        final AtomicInteger calls = new AtomicInteger();
+        final int totalCalls = 1_000_000; // 400_000; // 1_000_000; // 10_000_000;
+
+        EventHandler<Event> handler = new EventHandler<Event>()
+        {
+            @Override
+            public void handleEvent( @Nonnull Event event )
+            {
+                if ( staredAt[0] == 0 )
+                {
+                    staredAt[0] = System.currentTimeMillis();
+                }
+
+                if ( calls.incrementAndGet() == totalCalls )
+                {
+                    finishedAt[0] = System.currentTimeMillis();
+                }
+            }
+        };
+
+        final ByteBuffer event = ByteBuffer.allocate( 192 );
+        event.put( ":maven-surefire-event:".getBytes( UTF_8 ) );
+        event.put( (byte) 14 );
+        event.put( ":std-out-stream:".getBytes( UTF_8 ) );
+        event.put( (byte) 10 );
+        event.put( ":normal-run:".getBytes( UTF_8 ) );
+        event.put( (byte) 5 );
+        event.put( ":UTF-8:".getBytes( UTF_8 ) );
+        event.putInt( 100 );
+        event.put(
+            ":0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789:"
+                .getBytes( UTF_8 ) );
+
+        event.flip();
+        byte[] frame = copyOfRange( event.array(), event.arrayOffset(), event.arrayOffset() + event.remaining() );
+        ReadableByteChannel channel = new Channel( frame, 1024 )
+        {
+            private int countRounds;
+
+            @Override
+            public int read( ByteBuffer dst )
+            {
+                int length = super.read( dst );
+                if ( length == -1 && countRounds < totalCalls )
+                {
+                    i = 0;
+                    length = super.read( dst );
+                    countRounds++;
+                }
+                return length;
+            }
+        };
+
+        EventConsumerThread thread = new EventConsumerThread( "t", channel, handler,
+            new CountdownCloseable( new MockCloseable(), 1 ), new MockForkNodeArguments() );
+
+        TimeUnit.SECONDS.sleep( 2 );
+        System.gc();
+        TimeUnit.SECONDS.sleep( 5 );
+
+        System.out.println( "Staring the event thread..." );
+
+        thread.start();
+        thread.join();
+
+        long execTime = finishedAt[0] - staredAt[0];
+        System.out.println( execTime );
+
+        // 0.6 seconds while using the encoder/decoder
+        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." )
+            .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" );
+    }
+
+    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 MockCloseable implements Closeable
+    {
+        @Override
+        public void close()
+        {
+        }
+    }
+
+    private static class MockEventHandler<T> implements EventHandler<T>
+    {
+        @Override
+        public void handleEvent( @Nonnull T event )
+        {
+        }
+    }
+
+    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;
+        }
+    }
+
+    private static class InOrder extends Condition<Object[]>
+    {
+        private final SegmentType[] expected;
+
+        InOrder( SegmentType... expected )
+        {
+            this.expected = expected;
+        }
+
+        @Override
+        public boolean matches( Object[] values )
+        {
+            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;
+        }
+    }
+}
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 9d38b65..b83884a 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
@@ -62,7 +62,6 @@ import java.io.File;
 import java.io.LineNumberReader;
 import java.io.PrintStream;
 import java.io.StringReader;
-import java.nio.ByteBuffer;
 import java.nio.channels.ReadableByteChannel;
 import java.nio.charset.Charset;
 import java.util.Map;
@@ -74,19 +73,18 @@ import java.util.concurrent.atomic.AtomicInteger;
 
 import static java.lang.String.format;
 import static java.nio.charset.StandardCharsets.UTF_8;
-import static java.util.Arrays.copyOfRange;
 import static org.apache.maven.surefire.api.report.RunMode.NORMAL_RUN;
-import static org.apache.maven.surefire.shared.codec.binary.Base64.encodeBase64String;
 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;
-import static org.fest.assertions.Index.atIndex;
 import static org.junit.Assert.assertTrue;
 import static org.junit.rules.ExpectedException.none;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 /**
@@ -109,15 +107,6 @@ public class ForkedProcessEventNotifierTest
         public final ExpectedException rule = none();
 
         @Test
-        public void shouldBeFailSafe()
-        {
-            assertThat( EventConsumerThread.decode( null, UTF_8 ) ).isNull();
-            assertThat( EventConsumerThread.decode( "-", UTF_8 ) ).isNull();
-            assertThat( EventConsumerThread.decodeToInteger( null ) ).isNull();
-            assertThat( EventConsumerThread.decodeToInteger( "-" ) ).isNull();
-        }
-
-        @Test
         public void shouldHaveSystemProperty() throws Exception
         {
             final Stream out = Stream.newStream();
@@ -138,6 +127,7 @@ public class ForkedProcessEventNotifierTest
             ConsoleLogger logger = mock( ConsoleLogger.class );
             ForkNodeArguments arguments = mock( ForkNodeArguments.class );
             when( arguments.getConsoleLogger() ).thenReturn( logger );
+            when( arguments.dumpStreamText( anyString() ) ).thenReturn( new File( "" ) );
             try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, arguments ) )
             {
                 t.start();
@@ -146,7 +136,7 @@ public class ForkedProcessEventNotifierTest
                     notifier.notifyEvent( eventHandler.pullEvent() );
                 }
             }
-
+            verifyZeroInteractions( logger );
             assertThat( listener.counter.get() )
                 .isEqualTo( props.size() );
         }
@@ -154,11 +144,7 @@ public class ForkedProcessEventNotifierTest
         @Test
         public void shouldRecognizeEmptyStream4ReportEntry()
         {
-            ReportEntry reportEntry = EventConsumerThread.decodeReportEntry( null, null, null, "", "", null, null, "",
-                    "", "", null );
-            assertThat( reportEntry ).isNull();
-
-            reportEntry = EventConsumerThread.decodeReportEntry( UTF_8, "", "", "", "", "", "", "-", "", "", "" );
+            ReportEntry reportEntry = EventConsumerThread.newReportEntry( "", "", "", "", "", "", null, "", "", "" );
             assertThat( reportEntry ).isNotNull();
             assertThat( reportEntry.getStackTraceWriter() ).isNotNull();
             assertThat( reportEntry.getStackTraceWriter().smartTrimmedStackTrace() ).isEmpty();
@@ -172,9 +158,6 @@ public class ForkedProcessEventNotifierTest
             assertThat( reportEntry.getNameWithGroup() ).isEmpty();
             assertThat( reportEntry.getMessage() ).isEmpty();
             assertThat( reportEntry.getElapsed() ).isNull();
-
-            rule.expect( NumberFormatException.class );
-            EventConsumerThread.decodeReportEntry( UTF_8, "", "", "", "", "", "", "", "", "", "" );
         }
 
         @Test
@@ -182,16 +165,9 @@ public class ForkedProcessEventNotifierTest
         public void testCreatingReportEntry()
         {
             final String exceptionMessage = "msg";
-            final String encodedExceptionMsg = encodeBase64String( toArray( UTF_8.encode( exceptionMessage ) ) );
-
             final String smartStackTrace = "MyTest:86 >> Error";
-            final String encodedSmartStackTrace = encodeBase64String( toArray( UTF_8.encode( smartStackTrace ) ) );
-
             final String stackTrace = "Exception: msg\ntrace line 1\ntrace line 2";
-            final String encodedStackTrace = encodeBase64String( toArray( UTF_8.encode( stackTrace ) ) );
-
             final String trimmedStackTrace = "trace line 1\ntrace line 2";
-            final String encodedTrimmedStackTrace = encodeBase64String( toArray( UTF_8.encode( trimmedStackTrace ) ) );
 
             SafeThrowable safeThrowable = new SafeThrowable( exceptionMessage );
             StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
@@ -211,15 +187,9 @@ public class ForkedProcessEventNotifierTest
             when( reportEntry.getSourceText() ).thenReturn( "test class display name" );
             when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
-            String encodedSourceName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getSourceName() ) ) );
-            String encodedSourceText = encodeBase64String( toArray( UTF_8.encode( reportEntry.getSourceText() ) ) );
-            String encodedName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getName() ) ) );
-            String encodedText = encodeBase64String( toArray( UTF_8.encode( reportEntry.getNameText() ) ) );
-            String encodedGroup = encodeBase64String( toArray( UTF_8.encode( reportEntry.getGroup() ) ) );
-            String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
-
-            ReportEntry decodedReportEntry = EventConsumerThread.decodeReportEntry( UTF_8, encodedSourceName,
-                encodedSourceText, encodedName, encodedText, encodedGroup, encodedMessage, "-", null, null, null );
+            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() );
@@ -230,9 +200,9 @@ public class ForkedProcessEventNotifierTest
             assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
             assertThat( decodedReportEntry.getStackTraceWriter() ).isNull();
 
-            decodedReportEntry = EventConsumerThread.decodeReportEntry( UTF_8, encodedSourceName, encodedSourceText,
-                encodedName, encodedText, encodedGroup, encodedMessage, "-", encodedExceptionMsg,
-                encodedSmartStackTrace, null );
+            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() );
@@ -250,9 +220,9 @@ public class ForkedProcessEventNotifierTest
             assertThat( decodedReportEntry.getStackTraceWriter().writeTraceToString() )
                 .isNull();
 
-            decodedReportEntry = EventConsumerThread.decodeReportEntry( UTF_8, encodedSourceName, encodedSourceText,
-                encodedName, encodedText, encodedGroup, encodedMessage, "1003", encodedExceptionMsg,
-                encodedSmartStackTrace, null );
+            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() );
@@ -270,9 +240,9 @@ public class ForkedProcessEventNotifierTest
             assertThat( decodedReportEntry.getStackTraceWriter().writeTraceToString() )
                 .isNull();
 
-            decodedReportEntry = EventConsumerThread.decodeReportEntry( UTF_8, encodedSourceName, encodedSourceText,
-                encodedName, encodedText, encodedGroup, encodedMessage, "1003", encodedExceptionMsg,
-                encodedSmartStackTrace, encodedStackTrace );
+            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() );
@@ -291,9 +261,9 @@ public class ForkedProcessEventNotifierTest
             assertThat( decodedReportEntry.getStackTraceWriter().writeTraceToString() ).isEqualTo( stackTrace );
             assertThat( decodedReportEntry.getStackTraceWriter().writeTrimmedTraceToString() ).isEqualTo( stackTrace );
 
-            decodedReportEntry = EventConsumerThread.decodeReportEntry( UTF_8, encodedSourceName, encodedSourceText,
-                encodedName, encodedText, encodedGroup, encodedMessage, "1003", encodedExceptionMsg,
-                encodedSmartStackTrace, encodedTrimmedStackTrace );
+            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() );
@@ -324,7 +294,7 @@ public class ForkedProcessEventNotifierTest
             String read = new String( out.toByteArray(), UTF_8 );
 
             assertThat( read )
-                    .isEqualTo( ":maven-surefire-event:bye:\n" );
+                    .isEqualTo( ":maven-surefire-event:\u0003:bye:" );
 
             LineNumberReader lines = out.newReader( UTF_8 );
 
@@ -343,12 +313,13 @@ public class ForkedProcessEventNotifierTest
             ConsoleLogger logger = mock( ConsoleLogger.class );
             ForkNodeArguments arguments = mock( ForkNodeArguments.class );
             when( arguments.getConsoleLogger() ).thenReturn( logger );
+            when( arguments.dumpStreamText( anyString() ) ).thenReturn( new File( "" ) );
             try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, arguments ) )
             {
                 t.start();
                 notifier.notifyEvent( eventHandler.pullEvent() );
             }
-
+            verifyZeroInteractions( logger );
             assertThat( listener.called.get() )
                 .isTrue();
         }
@@ -363,7 +334,7 @@ public class ForkedProcessEventNotifierTest
             String read = new String( out.toByteArray(), UTF_8 );
 
             assertThat( read )
-                    .isEqualTo( ":maven-surefire-event:stop-on-next-test:\n" );
+                    .isEqualTo( ":maven-surefire-event:\u0011:stop-on-next-test:" );
 
             LineNumberReader lines = out.newReader( UTF_8 );
 
@@ -382,12 +353,14 @@ public class ForkedProcessEventNotifierTest
             ConsoleLogger logger = mock( ConsoleLogger.class );
             ForkNodeArguments arguments = mock( ForkNodeArguments.class );
             when( arguments.getConsoleLogger() ).thenReturn( logger );
+            when( arguments.dumpStreamText( anyString() ) ).thenReturn( new File( "" ) );
             try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, arguments ) )
             {
                 t.start();
                 notifier.notifyEvent( eventHandler.pullEvent() );
             }
-
+            verifyZeroInteractions( logger );
+            verify( arguments, never() ).dumpStreamText( anyString() );
             assertThat( listener.called.get() )
                 .isTrue();
         }
@@ -438,7 +411,7 @@ public class ForkedProcessEventNotifierTest
                 t.start();
                 notifier.notifyEvent( eventHandler.pullEvent() );
             }
-
+            verifyZeroInteractions( logger );
             assertThat( listener.called.get() )
                 .isTrue();
         }
@@ -453,7 +426,7 @@ public class ForkedProcessEventNotifierTest
             String read = new String( out.toByteArray(), UTF_8 );
 
             assertThat( read )
-                    .isEqualTo( ":maven-surefire-event:next-test:\n" );
+                    .isEqualTo( ":maven-surefire-event:\u0009:next-test:" );
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
 
@@ -466,12 +439,13 @@ public class ForkedProcessEventNotifierTest
             ConsoleLogger logger = mock( ConsoleLogger.class );
             ForkNodeArguments arguments = mock( ForkNodeArguments.class );
             when( arguments.getConsoleLogger() ).thenReturn( logger );
+            when( arguments.dumpStreamText( anyString() ) ).thenReturn( new File( "" ) );
             try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, arguments ) )
             {
                 t.start();
                 notifier.notifyEvent( eventHandler.pullEvent() );
             }
-
+            verifyZeroInteractions( logger );
             assertThat( listener.called.get() )
                 .isTrue();
         }
@@ -495,12 +469,13 @@ public class ForkedProcessEventNotifierTest
             ConsoleLogger logger = mock( ConsoleLogger.class );
             ForkNodeArguments arguments = mock( ForkNodeArguments.class );
             when( arguments.getConsoleLogger() ).thenReturn( logger );
+            when( arguments.dumpStreamText( anyString() ) ).thenReturn( new File( "" ) );
             try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, arguments ) )
             {
                 t.start();
                 notifier.notifyEvent( eventHandler.pullEvent() );
             }
-
+            verifyZeroInteractions( logger );
             assertThat( listener.called.get() )
                 .isTrue();
         }
@@ -524,12 +499,13 @@ public class ForkedProcessEventNotifierTest
             ConsoleLogger logger = mock( ConsoleLogger.class );
             ForkNodeArguments arguments = mock( ForkNodeArguments.class );
             when( arguments.getConsoleLogger() ).thenReturn( logger );
+            when( arguments.dumpStreamText( anyString() ) ).thenReturn( new File( "" ) );
             try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, arguments ) )
             {
                 t.start();
                 notifier.notifyEvent( eventHandler.pullEvent() );
             }
-
+            verifyZeroInteractions( logger );
             assertThat( listener.called.get() )
                 .isTrue();
         }
@@ -555,12 +531,13 @@ public class ForkedProcessEventNotifierTest
             ConsoleLogger logger = mock( ConsoleLogger.class );
             ForkNodeArguments arguments = mock( ForkNodeArguments.class );
             when( arguments.getConsoleLogger() ).thenReturn( logger );
+            when( arguments.dumpStreamText( anyString() ) ).thenReturn( new File( "" ) );
             try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, arguments ) )
             {
                 t.start();
                 notifier.notifyEvent( eventHandler.pullEvent() );
             }
-
+            verifyZeroInteractions( logger );
             assertThat( listener.called.get() )
                 .isTrue();
         }
@@ -586,12 +563,13 @@ public class ForkedProcessEventNotifierTest
             ConsoleLogger logger = mock( ConsoleLogger.class );
             ForkNodeArguments arguments = mock( ForkNodeArguments.class );
             when( arguments.getConsoleLogger() ).thenReturn( logger );
+            when( arguments.dumpStreamText( anyString() ) ).thenReturn( new File( "" ) );
             try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, arguments ) )
             {
                 t.start();
                 notifier.notifyEvent( eventHandler.pullEvent() );
             }
-
+            verifyZeroInteractions( logger );
             assertThat( listener.called.get() )
                 .isTrue();
         }
@@ -616,12 +594,15 @@ public class ForkedProcessEventNotifierTest
             ConsoleLogger logger = mock( ConsoleLogger.class );
             ForkNodeArguments arguments = mock( ForkNodeArguments.class );
             when( arguments.getConsoleLogger() ).thenReturn( logger );
+            when( arguments.dumpStreamText( anyString() ) ).thenReturn( new File( "" ) );
             try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, arguments ) )
             {
                 t.start();
                 notifier.notifyEvent( eventHandler.pullEvent() );
             }
 
+            verifyZeroInteractions( logger );
+
             assertThat( listener.called.get() )
                 .isTrue();
 
@@ -649,12 +630,13 @@ public class ForkedProcessEventNotifierTest
             ConsoleLogger logger = mock( ConsoleLogger.class );
             ForkNodeArguments arguments = mock( ForkNodeArguments.class );
             when( arguments.getConsoleLogger() ).thenReturn( logger );
+            when( arguments.dumpStreamText( anyString() ) ).thenReturn( new File( "" ) );
             try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, arguments ) )
             {
                 t.start();
                 notifier.notifyEvent( eventHandler.pullEvent() );
             }
-
+            verifyZeroInteractions( logger );
             assertThat( listener.called.get() )
                 .isTrue();
         }
@@ -680,12 +662,13 @@ public class ForkedProcessEventNotifierTest
             ConsoleLogger logger = mock( ConsoleLogger.class );
             ForkNodeArguments arguments = mock( ForkNodeArguments.class );
             when( arguments.getConsoleLogger() ).thenReturn( logger );
+            when( arguments.dumpStreamText( anyString() ) ).thenReturn( new File( "" ) );
             try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, arguments ) )
             {
                 t.start();
                 notifier.notifyEvent( eventHandler.pullEvent() );
             }
-
+            verifyZeroInteractions( logger );
             assertThat( listener.called.get() )
                 .isTrue();
         }
@@ -711,12 +694,13 @@ public class ForkedProcessEventNotifierTest
             ConsoleLogger logger = mock( ConsoleLogger.class );
             ForkNodeArguments arguments = mock( ForkNodeArguments.class );
             when( arguments.getConsoleLogger() ).thenReturn( logger );
+            when( arguments.dumpStreamText( anyString() ) ).thenReturn( new File( "" ) );
             try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, arguments ) )
             {
                 t.start();
                 notifier.notifyEvent( eventHandler.pullEvent() );
             }
-
+            verifyZeroInteractions( logger );
             assertThat( listener.called.get() )
                 .isTrue();
         }
@@ -747,7 +731,7 @@ public class ForkedProcessEventNotifierTest
                 t.start();
                 notifier.notifyEvent( eventHandler.pullEvent() );
             }
-
+            verifyZeroInteractions( logger );
             assertThat( listener.called.get() )
                 .isTrue();
         }
@@ -778,7 +762,7 @@ public class ForkedProcessEventNotifierTest
                 t.start();
                 notifier.notifyEvent( eventHandler.pullEvent() );
             }
-
+            verifyZeroInteractions( logger );
             assertThat( listener.called.get() )
                 .isTrue();
         }
@@ -809,7 +793,7 @@ public class ForkedProcessEventNotifierTest
                 t.start();
                 notifier.notifyEvent( eventHandler.pullEvent() );
             }
-
+            verifyZeroInteractions( logger );
             assertThat( listener.called.get() )
                 .isTrue();
         }
@@ -835,12 +819,13 @@ public class ForkedProcessEventNotifierTest
             ConsoleLogger logger = mock( ConsoleLogger.class );
             ForkNodeArguments arguments = mock( ForkNodeArguments.class );
             when( arguments.getConsoleLogger() ).thenReturn( logger );
+            when( arguments.dumpStreamText( anyString() ) ).thenReturn( new File( "" ) );
             try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, arguments ) )
             {
                 t.start();
                 notifier.notifyEvent( eventHandler.pullEvent() );
             }
-
+            verifyZeroInteractions( logger );
             assertThat( listener.called.get() )
                 .isTrue();
         }
@@ -865,12 +850,13 @@ public class ForkedProcessEventNotifierTest
             ConsoleLogger logger = mock( ConsoleLogger.class );
             ForkNodeArguments arguments = mock( ForkNodeArguments.class );
             when( arguments.getConsoleLogger() ).thenReturn( logger );
+            when( arguments.dumpStreamText( anyString() ) ).thenReturn( new File( "" ) );
             try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, arguments ) )
             {
                 t.start();
                 notifier.notifyEvent( eventHandler.pullEvent() );
             }
-
+            verifyZeroInteractions( logger );
             assertThat( listener.called.get() )
                 .isTrue();
         }
@@ -887,7 +873,7 @@ public class ForkedProcessEventNotifierTest
         @Test
         public void shouldHandleErrorAfterUnknownOperation() throws Exception
         {
-            String cmd = ":maven-surefire-event:abnormal-run:-:\n";
+            String cmd = ":maven-surefire-event:\u000c:abnormal-run:-:\n";
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( cmd.getBytes() ) );
 
@@ -905,29 +891,25 @@ public class ForkedProcessEventNotifierTest
             }
 
             ArgumentCaptor<String> dumpLine = ArgumentCaptor.forClass( String.class );
-            verify( logger, times( 2 ) ).debug( dumpLine.capture() );
+            verify( logger, times( 1 ) ).debug( dumpLine.capture() );
             assertThat( dumpLine.getAllValues() )
-                .hasSize( 2 )
-                .contains( ":maven-surefire-event:abnormal-run:", atIndex( 0 ) )
-                .contains( "-:", atIndex( 1 ) );
+                .hasSize( 1 )
+                .contains( ":maven-surefire-event:\u000c:abnormal-run:-:" );
 
             ArgumentCaptor<String> dumpText = ArgumentCaptor.forClass( String.class );
-            verify( arguments, times( 2 ) ).dumpStreamText( dumpText.capture() );
+            verify( arguments, times( 1 ) ).dumpStreamText( dumpText.capture() );
             String dump = "Corrupted STDOUT by directly writing to native stream in forked JVM 0.";
             assertThat( dumpText.getAllValues() )
-                .hasSize( 2 )
-                .contains( format( dump + " Stream '%s'.", ":maven-surefire-event:abnormal-run:" ), atIndex( 0 ) )
-                .contains( format( dump + " Stream '%s'.", "-:" ), atIndex( 1 ) );
+                .hasSize( 1 )
+                .contains( format( dump + " Stream '%s'.", ":maven-surefire-event:\u000c:abnormal-run:-:" ) );
 
             ArgumentCaptor<String> warning = ArgumentCaptor.forClass( String.class );
-            verify( arguments, times( 2 ) ).logWarningAtEnd( warning.capture() );
+            verify( arguments, times( 1 ) ).logWarningAtEnd( warning.capture() );
             dump += " See FAQ web page and the dump file ";
             assertThat( warning.getAllValues() )
-                .hasSize( 2 );
+                .hasSize( 1 );
             assertThat( warning.getAllValues().get( 0 ) )
                 .startsWith( dump );
-            assertThat( warning.getAllValues().get( 1 ) )
-                .startsWith( dump );
         }
 
         @Test
@@ -955,12 +937,15 @@ public class ForkedProcessEventNotifierTest
             ConsoleLogger logger = mock( ConsoleLogger.class );
             ForkNodeArguments arguments = mock( ForkNodeArguments.class );
             when( arguments.getConsoleLogger() ).thenReturn( logger );
+            when( arguments.dumpStreamText( anyString() ) ).thenReturn( new File( "" ) );
             try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, arguments ) )
             {
                 t.start();
                 notifier.notifyEvent( eventHandler.pullEvent() );
             }
 
+            verifyZeroInteractions( logger );
+
             assertThat( listener.called.get() )
                 .isTrue();
         }
@@ -1065,12 +1050,14 @@ public class ForkedProcessEventNotifierTest
             CountdownCloseable countdown = new CountdownCloseable( mock( Closeable.class ), 0 );
             ConsoleLogger logger = mock( ConsoleLogger.class );
             ForkNodeArguments arguments = mock( ForkNodeArguments.class );
+            when( arguments.dumpStreamText( anyString() ) ).thenReturn( new File( "" ) );
             when( arguments.getConsoleLogger() ).thenReturn( logger );
             try ( EventConsumerThread t = new EventConsumerThread( "t", channel, eventHandler, countdown, arguments ) )
             {
                 t.start();
                 notifier.notifyEvent( eventHandler.pullEvent() );
             }
+            verifyZeroInteractions( logger );
         }
     }
 
@@ -1261,11 +1248,6 @@ public class ForkedProcessEventNotifierTest
         }
     }
 
-    private static byte[] toArray( ByteBuffer buffer )
-    {
-        return copyOfRange( buffer.array(), buffer.arrayOffset(), buffer.arrayOffset() + buffer.remaining() );
-    }
-
     private static class EH implements EventHandler<Event>
     {
         private final BlockingQueue<Event> cache = new LinkedTransferQueue<>();
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 9770f8a..7692a04 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
@@ -44,6 +44,7 @@ import org.apache.maven.plugin.surefire.booterclient.output.ForkClientTest;
 import org.apache.maven.plugin.surefire.booterclient.output.ThreadedStreamConsumerTest;
 import org.apache.maven.plugin.surefire.extensions.ConsoleOutputReporterTest;
 import org.apache.maven.plugin.surefire.extensions.E2ETest;
+import org.apache.maven.plugin.surefire.extensions.EventConsumerThreadTest;
 import org.apache.maven.plugin.surefire.extensions.ForkedProcessEventNotifierTest;
 import org.apache.maven.plugin.surefire.extensions.StatelessReporterTest;
 import org.apache.maven.plugin.surefire.extensions.StreamFeederTest;
@@ -114,6 +115,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( EventConsumerThreadTest.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 16eb3a5..8caa668 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
@@ -63,6 +63,7 @@ public class ForkChannelTest
     @Test( timeout = TESTCASE_TIMEOUT )
     public void shouldRequestReplyMessagesViaTCP() throws Exception
     {
+        final MockReporter reporter = new MockReporter();
         final String sessionId = UUID.randomUUID().toString();
         ForkNodeArguments forkNodeArguments = new ForkNodeArguments()
         {
@@ -86,6 +87,13 @@ public class ForkChannelTest
                 return new File( "" );
             }
 
+            @Nonnull
+            @Override
+            public File dumpStreamException( @Nonnull Throwable t )
+            {
+                return new File( "" );
+            }
+
             @Override
             public void logWarningAtEnd( @Nonnull String text )
             {
@@ -95,7 +103,7 @@ public class ForkChannelTest
             @Nonnull
             public ConsoleLogger getConsoleLogger()
             {
-                return new MockReporter();
+                return reporter;
             }
         };
 
@@ -133,7 +141,7 @@ public class ForkChannelTest
             CountdownCloseable cc = new CountdownCloseable( closeable, 2 );
             Consumer consumer = new Consumer();
 
-            Client client = new Client( uri.getPort(), sessionId.toString() );
+            Client client = new Client( uri.getPort(), sessionId );
             client.start();
 
             channel.connectToClient();
@@ -152,6 +160,10 @@ public class ForkChannelTest
             assertThat( isCloseableCalled.await( TESTCASE_TIMEOUT, MILLISECONDS ) )
                 .isTrue();
 
+            assertThat( reporter.getEvents() )
+                .describedAs( "The decoder captured the list of stream errors: " + reporter.getData().toString() )
+                .isEmpty();
+
             assertThat( consumer.lines )
                 .hasSize( 1 );
 
@@ -192,7 +204,7 @@ public class ForkChannelTest
                 int readLength = socket.getInputStream().read( data );
                 String token = new String( data, 0, readLength, US_ASCII );
                 assertThat( token ).isEqualTo( ":maven-surefire-command:noop:" );
-                socket.getOutputStream().write( ":maven-surefire-event:bye:".getBytes( US_ASCII ) );
+                socket.getOutputStream().write( ":maven-surefire-event:\u0003:bye:".getBytes( US_ASCII ) );
             }
             catch ( IOException e )
             {
diff --git a/surefire-its/src/test/resources/surefire-224-wellFormedXmlFailures/src/test/java/wellFormedXmlFailures/TestSurefire3.java b/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/Constants.java
similarity index 57%
copy from surefire-its/src/test/resources/surefire-224-wellFormedXmlFailures/src/test/java/wellFormedXmlFailures/TestSurefire3.java
copy to surefire-api/src/main/java/org/apache/maven/surefire/api/booter/Constants.java
index 7bb1afe..d1be7ac 100644
--- a/surefire-its/src/test/resources/surefire-224-wellFormedXmlFailures/src/test/java/wellFormedXmlFailures/TestSurefire3.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/Constants.java
@@ -1,4 +1,4 @@
-package wellFormedXmlFailures;
+package org.apache.maven.surefire.api.booter;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,44 +19,17 @@ package wellFormedXmlFailures;
  * under the License.
  */
 
-import junit.extensions.TestSetup;
-import junit.framework.Test;
-import junit.framework.TestCase;
-import junit.framework.TestSuite;
+import java.nio.charset.Charset;
 
-public class TestSurefire3
-    extends TestCase
-{
-
-    public TestSurefire3( )
-    {
-        super( );
-    }
-
-    public TestSurefire3( String name )
-    {
-        super( name );
-    }
-
-
-    public void testQuote()
-    {
-        fail( "\"" );
-    }
-
-    public void testLower()
-    {
-        fail( "<" );
-    }
-
-    public void testGreater()
-    {
-        fail( ">" );
-    }
-
-    public void testU0000()
-    {
-        fail( "\u0000" );
-    }
+import static java.nio.charset.StandardCharsets.US_ASCII;
+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 );
+    public static final Charset DEFAULT_STREAM_ENCODING = UTF_8;
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/ForkedProcessEventType.java b/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/ForkedProcessEventType.java
index e84e5bc..94da335 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/ForkedProcessEventType.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/ForkedProcessEventType.java
@@ -19,11 +19,7 @@ package org.apache.maven.surefire.api.booter;
  * under the License.
  */
 
-import javax.annotation.Nonnull;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-import static java.util.Collections.unmodifiableMap;
+import static java.nio.charset.StandardCharsets.US_ASCII;
 
 /**
  * Events sent back to the plugin process.
@@ -31,54 +27,268 @@ import static java.util.Collections.unmodifiableMap;
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
  * @since 3.0.0-M4
  */
+@SuppressWarnings( "checkstyle:linelength" )
 public enum ForkedProcessEventType
 {
-    BOOTERCODE_SYSPROPS( "sys-prop" ),
+    /**
+     * This is the opcode "sys-prop". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:sys-prop:RunMode:UTF-8:0xFFFFFFFF:key:0xFFFFFFFF:value:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "sys-prop"
+     * </ul>
+     */
+    BOOTERCODE_SYSPROPS( "sys-prop"  ),
 
+    /**
+     * This is the opcode "testset-starting". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:testset-starting:RunMode:UTF-8:0xFFFFFFFF:SourceName:0xFFFFFFFF:SourceText:0xFFFFFFFF:Name:0xFFFFFFFF:NameText:0xFFFFFFFF:Group:0xFFFFFFFF:Message:ElapsedTime (binary int):0xFFFFFFFF:LocalizedMessage:0xFFFFFFFF:SmartTrimmedStackTrace:0xFFFFFFFF:toStackTrace( stw, trimStackTraces ):
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "testset-starting"
+     * </ul>
+     */
     BOOTERCODE_TESTSET_STARTING( "testset-starting" ),
+
+    /**
+     * This is the opcode "testset-completed". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:testset-completed:RunMode:UTF-8:0xFFFFFFFF:SourceName:0xFFFFFFFF:SourceText:0xFFFFFFFF:Name:0xFFFFFFFF:NameText:0xFFFFFFFF:Group:0xFFFFFFFF:Message:ElapsedTime (binary int):0xFFFFFFFF:LocalizedMessage:0xFFFFFFFF:SmartTrimmedStackTrace:0xFFFFFFFF:toStackTrace( stw, trimStackTraces ):
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "testset-completed"
+     * </ul>
+     */
     BOOTERCODE_TESTSET_COMPLETED( "testset-completed" ),
+
+    /**
+     * This is the opcode "test-starting". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:test-starting:RunMode:UTF-8:0xFFFFFFFF:SourceName:0xFFFFFFFF:SourceText:0xFFFFFFFF:Name:0xFFFFFFFF:NameText:0xFFFFFFFF:Group:0xFFFFFFFF:Message:ElapsedTime (binary int):0xFFFFFFFF:LocalizedMessage:0xFFFFFFFF:SmartTrimmedStackTrace:0xFFFFFFFF:toStackTrace( stw, trimStackTraces ):
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "test-starting"
+     * </ul>
+     */
     BOOTERCODE_TEST_STARTING( "test-starting" ),
+
+    /**
+     * This is the opcode "test-succeeded". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:test-succeeded:RunMode:UTF-8:0xFFFFFFFF:SourceName:0xFFFFFFFF:SourceText:0xFFFFFFFF:Name:0xFFFFFFFF:NameText:0xFFFFFFFF:Group:0xFFFFFFFF:Message:ElapsedTime (binary int):0xFFFFFFFF:LocalizedMessage:0xFFFFFFFF:SmartTrimmedStackTrace:0xFFFFFFFF:toStackTrace( stw, trimStackTraces ):
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "test-succeeded"
+     * </ul>
+     */
     BOOTERCODE_TEST_SUCCEEDED( "test-succeeded" ),
+
+    /**
+     * This is the opcode "test-failed". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:test-failed:RunMode:UTF-8:0xFFFFFFFF:SourceName:0xFFFFFFFF:SourceText:0xFFFFFFFF:Name:0xFFFFFFFF:NameText:0xFFFFFFFF:Group:0xFFFFFFFF:Message:ElapsedTime (binary int):0xFFFFFFFF:LocalizedMessage:0xFFFFFFFF:SmartTrimmedStackTrace:0xFFFFFFFF:toStackTrace( stw, trimStackTraces ):
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "test-failed"
+     * </ul>
+     */
     BOOTERCODE_TEST_FAILED( "test-failed" ),
+
+    /**
+     * This is the opcode "test-skipped". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:test-skipped:RunMode:UTF-8:0xFFFFFFFF:SourceName:0xFFFFFFFF:SourceText:0xFFFFFFFF:Name:0xFFFFFFFF:NameText:0xFFFFFFFF:Group:0xFFFFFFFF:Message:ElapsedTime (binary int):0xFFFFFFFF:LocalizedMessage:0xFFFFFFFF:SmartTrimmedStackTrace:0xFFFFFFFF:toStackTrace( stw, trimStackTraces ):
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "test-skipped"
+     * </ul>
+     */
     BOOTERCODE_TEST_SKIPPED( "test-skipped" ),
+
+    /**
+     * This is the opcode "test-error". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:test-error:RunMode:UTF-8:0xFFFFFFFF:SourceName:0xFFFFFFFF:SourceText:0xFFFFFFFF:Name:0xFFFFFFFF:NameText:0xFFFFFFFF:Group:0xFFFFFFFF:Message:ElapsedTime (binary int):0xFFFFFFFF:LocalizedMessage:0xFFFFFFFF:SmartTrimmedStackTrace:0xFFFFFFFF:toStackTrace( stw, trimStackTraces ):
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "test-error"
+     * </ul>
+     */
     BOOTERCODE_TEST_ERROR( "test-error" ),
+
+    /**
+     * This is the opcode "test-assumption-failure". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:test-assumption-failure:RunMode:UTF-8:0xFFFFFFFF:SourceName:0xFFFFFFFF:SourceText:0xFFFFFFFF:Name:0xFFFFFFFF:NameText:0xFFFFFFFF:Group:0xFFFFFFFF:Message:ElapsedTime (binary int):0xFFFFFFFF:LocalizedMessage:0xFFFFFFFF:SmartTrimmedStackTrace:0xFFFFFFFF:toStackTrace( stw, trimStackTraces ):
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "test-assumption-failure"
+     * </ul>
+     */
     BOOTERCODE_TEST_ASSUMPTIONFAILURE( "test-assumption-failure" ),
 
+    /**
+     * This is the opcode "std-out-stream". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:std-out-stream:RunMode:UTF-8:0xFFFFFFFF:line:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "std-out-stream"
+     * </ul>
+     */
     BOOTERCODE_STDOUT( "std-out-stream" ),
+
+    /**
+     * This is the opcode "std-out-stream-new-line". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:std-out-stream-new-line:RunMode:UTF-8:0xFFFFFFFF:line:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "std-out-stream-new-line"
+     * </ul>
+     */
     BOOTERCODE_STDOUT_NEW_LINE( "std-out-stream-new-line" ),
+
+    /**
+     * This is the opcode "std-err-stream". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:std-err-stream:RunMode:UTF-8:0xFFFFFFFF:line:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "std-err-stream"
+     * </ul>
+     */
     BOOTERCODE_STDERR( "std-err-stream" ),
+
+    /**
+     * This is the opcode "std-err-stream-new-line". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:std-err-stream-new-line:RunMode:UTF-8:0xFFFFFFFF:line:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "std-err-stream-new-line"
+     * </ul>
+     */
     BOOTERCODE_STDERR_NEW_LINE( "std-err-stream-new-line" ),
 
+    /**
+     * This is the opcode "console-info-log". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:console-info-log:RunMode:UTF-8:0xFFFFFFFF:line:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "console-info-log"
+     * </ul>
+     */
     BOOTERCODE_CONSOLE_INFO( "console-info-log" ),
+
+    /**
+     * This is the opcode "console-debug-log". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:console-debug-log:RunMode:UTF-8:0xFFFFFFFF:line:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "console-debug-log"
+     * </ul>
+     */
     BOOTERCODE_CONSOLE_DEBUG( "console-debug-log" ),
+
+    /**
+     * This is the opcode "console-warning-log". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:console-warning-log:RunMode:UTF-8:0xFFFFFFFF:line:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "console-warning-log"
+     * </ul>
+     */
     BOOTERCODE_CONSOLE_WARNING( "console-warning-log" ),
+
+    /**
+     * This is the opcode "console-error-log". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:console-error-log:RunMode:UTF-8:0xFFFFFFFF:line:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "console-error-log"
+     * </ul>
+     */
     BOOTERCODE_CONSOLE_ERROR( "console-error-log" ),
 
+    /**
+     * This is the opcode "bye". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:bye:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "bye"
+     * </ul>
+     */
     BOOTERCODE_BYE( "bye" ),
+
+    /**
+     * This is the opcode "stop-on-next-test". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:stop-on-next-test:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "stop-on-next-test"
+     * </ul>
+     */
     BOOTERCODE_STOP_ON_NEXT_TEST( "stop-on-next-test" ),
+
+    /**
+     * This is the opcode "next-test". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:next-test:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "next-test"
+     * </ul>
+     */
     BOOTERCODE_NEXT_TEST( "next-test" ),
 
+    /**
+     * This is the opcode "jvm-exit-error". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:jvm-exit-error:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "jvm-exit-error"
+     * </ul>
+     */
     BOOTERCODE_JVM_EXIT_ERROR( "jvm-exit-error" );
 
-    public static final String MAGIC_NUMBER = "maven-surefire-event";
-
-    private static final Map<String, ForkedProcessEventType> EVENTS = events();
-
-    private static Map<String, ForkedProcessEventType> events()
-    {
-        Map<String, ForkedProcessEventType> events = new ConcurrentHashMap<>();
-        for ( ForkedProcessEventType event : values() )
-        {
-            events.put( event.getOpcode(), event );
-        }
-        return unmodifiableMap( events );
-    }
-
     private final String opcode;
+    private final byte[] opcodeBinary;
 
     ForkedProcessEventType( String opcode )
     {
         this.opcode = opcode;
+        opcodeBinary = opcode.getBytes( US_ASCII );
     }
 
     public String getOpcode()
@@ -86,6 +296,11 @@ public enum ForkedProcessEventType
         return opcode;
     }
 
+    public byte[] getOpcodeBinary()
+    {
+        return opcodeBinary;
+    }
+
     public boolean isSysPropCategory()
     {
         return this == BOOTERCODE_SYSPROPS;
@@ -130,9 +345,4 @@ public enum ForkedProcessEventType
     {
         return this == BOOTERCODE_JVM_EXIT_ERROR;
     }
-
-    public static ForkedProcessEventType byOpcode( @Nonnull String opcode )
-    {
-        return EVENTS.get( opcode );
-    }
 }
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 59aff6e..7329f9c 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,10 +19,7 @@ package org.apache.maven.surefire.api.report;
  * under the License.
  */
 
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-import static java.util.Collections.unmodifiableMap;
+import static java.nio.charset.StandardCharsets.US_ASCII;
 
 /**
  * Determines the purpose the provider started the tests. It can be either normal run or a kind of re-run type.
@@ -38,27 +35,22 @@ 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
 
-    public static final Map<String, RunMode> MODES = modes();
+    private final String runmode;
+    private final byte[] runmodeBinary;
 
-    private static Map<String, RunMode> modes()
+    RunMode( String runmode )
     {
-        Map<String, RunMode> modes = new ConcurrentHashMap<>();
-        for ( RunMode mode : values() )
-        {
-            modes.put( mode.geRunName(), mode );
-        }
-        return unmodifiableMap( modes );
+        this.runmode = runmode;
+        runmodeBinary = runmode.getBytes( US_ASCII );
     }
 
-    private final String runName;
-
-    RunMode( String runName )
+    public String geRunmode()
     {
-        this.runName = runName;
+        return runmode;
     }
 
-    public String geRunName()
+    public byte[] getRunmodeBinary()
     {
-        return runName;
+        return runmodeBinary;
     }
 }
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
index 315d350..9d29495 100644
--- 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
@@ -77,7 +77,7 @@ public class LegacyMasterProcessChannelDecoder implements MasterProcessChannelDe
             tokens.clear();
             token.setLength( 0 );
             FrameCompletion completion = null;
-            for ( boolean frameStarted = false; !( endOfStream = channel.read( buffer ) == -1 ) ; completion = null )
+            for ( boolean frameStarted = false; !( endOfStream = channel.read( buffer ) == -1 ); completion = null )
             {
                 buffer.flip();
                 char c = (char) buffer.get();
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/LegacyMasterProcessChannelEncoder.java
index 3cbc8eb..f8cb224 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/LegacyMasterProcessChannelEncoder.java
@@ -27,21 +27,23 @@ 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.shared.codec.binary.Base64;
 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.ClosedChannelException;
-import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.concurrent.atomic.AtomicBoolean;
 
-import static java.nio.charset.StandardCharsets.US_ASCII;
-import static java.nio.charset.StandardCharsets.UTF_8;
+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.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;
@@ -63,7 +65,6 @@ import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTER
 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.booter.ForkedProcessEventType.MAGIC_NUMBER;
 import static org.apache.maven.surefire.api.report.RunMode.NORMAL_RUN;
 import static org.apache.maven.surefire.api.report.RunMode.RERUN_TEST_AFTER_FAILURE;
 
@@ -74,11 +75,12 @@ import static org.apache.maven.surefire.api.report.RunMode.RERUN_TEST_AFTER_FAIL
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
  * @since 3.0.0-M4
  */
+@SuppressWarnings( "checkstyle:linelength" )
 public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEncoder
 {
-    private static final Base64 BASE64 = new Base64();
-    private static final Charset STREAM_ENCODING = US_ASCII;
-    private static final Charset STRING_ENCODING = UTF_8;
+    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;
@@ -118,18 +120,27 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
     public void onJvmExit()
     {
         onExit = true;
-        encodeAndPrintEvent( new StringBuilder( "\n" ), true );
+        encodeAndPrintEvent( ByteBuffer.wrap( new byte[] {'\n'} ), true );
     }
 
     @Override
     public void sendSystemProperties( Map<String, String> sysProps )
     {
-        for ( Entry<String, String> entry : sysProps.entrySet() )
+        CharsetEncoder encoder = DEFAULT_STREAM_ENCODING.newEncoder();
+        ByteBuffer result = null;
+        for ( Iterator<Entry<String, String>> it = sysProps.entrySet().iterator(); it.hasNext(); )
         {
+            Entry<String, String> entry = it.next();
             String key = entry.getKey();
             String value = entry.getValue();
-            StringBuilder event = encode( BOOTERCODE_SYSPROPS, runMode, key, value );
-            encodeAndPrintEvent( event, false );
+
+            int bufferLength = estimateBufferLength( BOOTERCODE_SYSPROPS, 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>:
+            encode( encoder, result, BOOTERCODE_SYSPROPS, runMode, key, value );
+            boolean sendImmediately = !it.hasNext();
+            encodeAndPrintEvent( result, sendImmediately );
         }
     }
 
@@ -185,36 +196,33 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
     public void stdOut( String msg, boolean newLine )
     {
         ForkedProcessEventType event = newLine ? BOOTERCODE_STDOUT_NEW_LINE : BOOTERCODE_STDOUT;
-        setOutErr( event.getOpcode(), msg );
+        setOutErr( event, msg );
     }
 
     @Override
     public void stdErr( String msg, boolean newLine )
     {
         ForkedProcessEventType event = newLine ? BOOTERCODE_STDERR_NEW_LINE : BOOTERCODE_STDERR;
-        setOutErr( event.getOpcode(), msg );
+        setOutErr( event, msg );
     }
 
-    private void setOutErr( String eventType, String message )
+    private void setOutErr( ForkedProcessEventType eventType, String message )
     {
-        String base64Message = toBase64( message );
-        StringBuilder event = encodeMessage( eventType, runMode.geRunName(), base64Message );
-        encodeAndPrintEvent( event, false );
+        ByteBuffer result = encodeMessage( eventType, runMode, message );
+        encodeAndPrintEvent( result, false );
     }
 
     @Override
-    public void consoleInfoLog( String msg )
+    public void consoleInfoLog( String message )
     {
-        StringBuilder event = print( BOOTERCODE_CONSOLE_INFO.getOpcode(), msg );
-        encodeAndPrintEvent( event, true );
+        ByteBuffer result = encodeMessage( BOOTERCODE_CONSOLE_INFO, null, message );
+        encodeAndPrintEvent( result, true );
     }
 
     @Override
-    public void consoleErrorLog( String msg )
+    public void consoleErrorLog( String message )
     {
-        StringBuilder encoded = encodeHeader( BOOTERCODE_CONSOLE_ERROR.getOpcode(), null );
-        encode( encoded, msg, null, null );
-        encodeAndPrintEvent( encoded, true );
+        consoleErrorLog( message, null );
     }
 
     @Override
@@ -224,11 +232,15 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
     }
 
     @Override
-    public void consoleErrorLog( String msg, Throwable t )
+    public void consoleErrorLog( String message, Throwable t )
     {
-        StringBuilder encoded = encodeHeader( BOOTERCODE_CONSOLE_ERROR.getOpcode(), null );
-        encode( encoded, msg, null, ConsoleLoggerUtils.toString( t ) );
-        encodeAndPrintEvent( encoded, true );
+        CharsetEncoder encoder = DEFAULT_STREAM_ENCODING.newEncoder();
+        String stackTrace = t == null ? null : ConsoleLoggerUtils.toString( t );
+        int bufferMaxLength = estimateBufferLength( BOOTERCODE_CONSOLE_ERROR, null, encoder, 0, message, stackTrace );
+        ByteBuffer result = ByteBuffer.allocate( bufferMaxLength );
+        encodeHeader( encoder, result, BOOTERCODE_CONSOLE_ERROR, null );
+        encode( encoder, result, message, null, stackTrace );
+        encodeAndPrintEvent( result, true );
     }
 
     @Override
@@ -238,17 +250,17 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
     }
 
     @Override
-    public void consoleDebugLog( String msg )
+    public void consoleDebugLog( String message )
     {
-        StringBuilder event = print( BOOTERCODE_CONSOLE_DEBUG.getOpcode(), msg );
-        encodeAndPrintEvent( event, true );
+        ByteBuffer result = encodeMessage( BOOTERCODE_CONSOLE_DEBUG, null, message );
+        encodeAndPrintEvent( result, true );
     }
 
     @Override
-    public void consoleWarningLog( String msg )
+    public void consoleWarningLog( String message )
     {
-        StringBuilder event = print( BOOTERCODE_CONSOLE_WARNING.getOpcode(), msg );
-        encodeAndPrintEvent( event, true );
+        ByteBuffer result = encodeMessage( BOOTERCODE_CONSOLE_WARNING, null, message );
+        encodeAndPrintEvent( result, true );
     }
 
     @Override
@@ -275,51 +287,61 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
         error( stackTraceWriter, trimStackTraces, BOOTERCODE_JVM_EXIT_ERROR, true );
     }
 
-    private void error( StackTraceWriter stackTraceWriter, boolean trimStackTraces, ForkedProcessEventType event,
+    private void error( StackTraceWriter stackTraceWriter, boolean trimStackTraces, ForkedProcessEventType eventType,
                         @SuppressWarnings( "SameParameterValue" ) boolean sendImmediately )
     {
-        StringBuilder encoded = encodeHeader( event.getOpcode(), null );
-        encode( encoded, stackTraceWriter, trimStackTraces );
-        encodeAndPrintEvent( encoded, sendImmediately );
+        CharsetEncoder encoder = DEFAULT_STREAM_ENCODING.newEncoder();
+        StackTrace stackTraceWrapper = new StackTrace( stackTraceWriter, trimStackTraces );
+        int bufferMaxLength = estimateBufferLength( eventType, null, encoder, 0,
+            stackTraceWrapper.message, stackTraceWrapper.smartTrimmedStackTrace, stackTraceWrapper.stackTrace );
+        ByteBuffer result = ByteBuffer.allocate( bufferMaxLength );
+
+        encodeHeader( encoder, result, eventType, null );
+        encode( encoder, result, stackTraceWrapper );
+        encodeAndPrintEvent( result, sendImmediately );
     }
 
+    /**
+     * :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:
+     *
+     */
     private void encode( ForkedProcessEventType operation, RunMode runMode, ReportEntry reportEntry,
                          boolean trimStackTraces, @SuppressWarnings( "SameParameterValue" ) boolean sendImmediately )
     {
-        StringBuilder event = encode( operation.getOpcode(), runMode.geRunName(), reportEntry, trimStackTraces );
-        encodeAndPrintEvent( event, sendImmediately );
+        ByteBuffer result = encode( operation, runMode, reportEntry, trimStackTraces );
+        encodeAndPrintEvent( result, sendImmediately );
     }
 
-    private void encodeOpcode( ForkedProcessEventType operation, boolean sendImmediately )
+    private void encodeOpcode( ForkedProcessEventType eventType, boolean sendImmediately )
     {
-        StringBuilder event = encodeOpcode( operation.getOpcode(), null );
-        encodeAndPrintEvent( event, sendImmediately );
+        CharsetEncoder encoder = DEFAULT_STREAM_ENCODING.newEncoder();
+        int bufferMaxLength = estimateBufferLength( eventType, null, null, 0 );
+        ByteBuffer result = ByteBuffer.allocate( bufferMaxLength );
+        encodeOpcode( encoder, result, eventType, null );
+        encodeAndPrintEvent( result, sendImmediately );
     }
 
-    private void encodeAndPrintEvent( StringBuilder event, boolean sendImmediately )
+    private void encodeAndPrintEvent( ByteBuffer frame, boolean sendImmediately )
     {
         final boolean wasInterrupted = Thread.interrupted();
         try
         {
-            byte[] array = event.append( '\n' )
-                .toString()
-                .getBytes( STREAM_ENCODING );
-
-            ByteBuffer bb = ByteBuffer.wrap( array );
-
             if ( sendImmediately )
             {
-                out.write( bb );
+                out.write( frame );
             }
             else
             {
-                out.writeBuffered( bb );
+                out.writeBuffered( frame );
             }
         }
         catch ( ClosedChannelException e )
         {
             if ( !onExit )
             {
+                String event = new String( frame.array(), frame.arrayOffset() + frame.position(), frame.remaining(),
+                    DEFAULT_STREAM_ENCODING );
+
                 DumpErrorSingleton.getSingleton()
                     .dumpException( e, "Channel closed while writing the event '" + event + "'." );
             }
@@ -341,36 +363,27 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
         }
     }
 
-    static StringBuilder encode( ForkedProcessEventType operation, RunMode runMode, String... args )
+    static void encode( CharsetEncoder encoder, ByteBuffer result,
+                        ForkedProcessEventType operation, RunMode runMode, String... messages )
     {
-        StringBuilder encodedTo = encodeHeader( operation.getOpcode(), runMode.geRunName() );
-
-        for ( int i = 0; i < args.length; )
+        encodeHeader( encoder, result, operation, runMode );
+        for ( String message : messages )
         {
-            String arg = args[i++];
-            encodedTo.append( toBase64( arg ) )
-                .append( ':' );
+            encodeString( encoder, result, message );
         }
-        return encodedTo;
     }
 
-    static void encode( StringBuilder encoded, StackTraceWriter stw, boolean trimStackTraces )
+    static void encode( CharsetEncoder encoder, ByteBuffer result, StackTrace stw )
     {
-        SafeThrowable throwable = stw == null ? null : stw.getThrowable();
-        String message = throwable == null ? null : throwable.getLocalizedMessage();
-        String smartStackTrace = stw == null ? null : stw.smartTrimmedStackTrace();
-        String stackTrace = stw == null ? null : toStackTrace( stw, trimStackTraces );
-        encode( encoded, message, smartStackTrace, stackTrace );
+        encode( encoder, result, stw.message, stw.smartTrimmedStackTrace, stw.stackTrace );
     }
 
-    private static void encode( StringBuilder encoded, String message, String smartStackTrace, String stackTrace )
+    private static void encode( CharsetEncoder encoder, ByteBuffer result,
+                                String message, String smartStackTrace, String stackTrace )
     {
-        encoded.append( toBase64( message ) )
-            .append( ':' )
-            .append( toBase64( smartStackTrace ) )
-            .append( ':' )
-            .append( toBase64( stackTrace ) )
-            .append( ':' );
+        encodeString( encoder, result, message );
+        encodeString( encoder, result, smartStackTrace );
+        encodeString( encoder, result, stackTrace );
     }
 
     /**
@@ -386,60 +399,86 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
      * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_ASSUMPTIONFAILURE}.</li>
      * </ul>
      */
-    static StringBuilder encode( String operation, String runMode, ReportEntry reportEntry, boolean trimStackTraces )
+    static ByteBuffer encode( ForkedProcessEventType operation, RunMode runMode, ReportEntry reportEntry,
+                              boolean trimStackTraces )
     {
-        StringBuilder encodedTo = encodeHeader( operation, runMode )
-                .append( toBase64( reportEntry.getSourceName() ) )
-                .append( ':' )
-                .append( toBase64( reportEntry.getSourceText() ) )
-                .append( ':' )
-                .append( toBase64( reportEntry.getName() ) )
-                .append( ':' )
-                .append( toBase64( reportEntry.getNameText() ) )
-                .append( ':' )
-                .append( toBase64( reportEntry.getGroup() ) )
-                .append( ':' )
-                .append( toBase64( reportEntry.getMessage() ) )
-                .append( ':' )
-                .append( reportEntry.getElapsed() == null ? "-" : reportEntry.getElapsed().toString() )
-                .append( ':' );
+        StackTrace stackTraceWrapper = new StackTrace( reportEntry.getStackTraceWriter(), trimStackTraces );
+
+        CharsetEncoder encoder = DEFAULT_STREAM_ENCODING.newEncoder();
+
+        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 );
+
+        ByteBuffer result = ByteBuffer.allocate( bufferMaxLength );
 
-        encode( encodedTo, reportEntry.getStackTraceWriter(), trimStackTraces );
+        encodeHeader( encoder, result, operation, runMode );
 
-        return encodedTo;
+        encodeString( encoder, result, reportEntry.getSourceName() );
+        encodeString( encoder, result, reportEntry.getSourceText() );
+        encodeString( encoder, result, reportEntry.getName() );
+        encodeString( encoder, result, reportEntry.getNameText() );
+        encodeString( encoder, result, reportEntry.getGroup() );
+        encodeString( encoder, result, reportEntry.getMessage() );
+        encodeInteger( encoder, result, reportEntry.getElapsed() );
+
+        encode( encoder, result, stackTraceWrapper );
+
+        return result;
     }
 
-    /**
-     * Used in {@link #consoleInfoLog(String)}, {@link #consoleErrorLog(String)}, {@link #consoleDebugLog(String)},
-     * {@link #consoleWarningLog(String)} and private methods extending the buffer.
-     */
-    StringBuilder print( String operation, String... msgs )
+    static ByteBuffer encodeMessage( ForkedProcessEventType eventType, RunMode runMode, String message )
     {
-        String[] encodedMsgs = new String[msgs.length];
-        for ( int i = 0; i < encodedMsgs.length; i++ )
-        {
-            String msg = msgs[i];
-            encodedMsgs[i] = toBase64( msg );
-        }
-        return encodeMessage( operation, null, encodedMsgs );
+        CharsetEncoder encoder = DEFAULT_STREAM_ENCODING.newEncoder();
+        int bufferMaxLength = estimateBufferLength( eventType, runMode, encoder, 0, message );
+        ByteBuffer result = ByteBuffer.allocate( bufferMaxLength );
+        encodeHeader( encoder, result, eventType, runMode );
+        encodeString( encoder, result, message );
+        return result;
     }
 
-    static StringBuilder encodeMessage( String operation, String runMode, String... encodedMsgs )
+    private static void encodeString( CharsetEncoder encoder, ByteBuffer result, String string )
     {
-        StringBuilder builder = encodeHeader( operation, runMode );
-        for ( String encodedMsg : encodedMsgs )
-        {
-            builder.append( encodedMsg ).append( ':' );
+        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( CharsetEncoder encoder, ByteBuffer result, Integer i )
+    {
+        if ( i == null )
+        {
+            result.put( BOOLEAN_NULL_OBJECT );
+        }
+        else
+        {
+            result.put( BOOLEAN_NON_NULL_OBJECT ).putInt( i );
         }
-        return builder;
+        result.put( (byte) ':' );
     }
 
-    static StringBuilder encodeHeader( String operation, String runMode )
+    static void encodeHeader( CharsetEncoder encoder, ByteBuffer result, ForkedProcessEventType operation,
+                              RunMode runMode )
     {
-        return encodeOpcode( operation, runMode )
-            .append( STRING_ENCODING.name() )
-            .append( ':' );
+        encodeOpcode( encoder, result, operation, runMode );
+        String charsetName = encoder.charset().name();
+        result.put( (byte) charsetName.length() );
+        result.put( (byte) ':' );
+        encoder.encode( wrap( charsetName ), result, true );
+        result.put( (byte) ':' );
     }
 
     /**
@@ -448,27 +487,91 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
      *
      * @param operation opcode
      * @param runMode   run mode
-     * @return encoded event
      */
-    static StringBuilder encodeOpcode( String operation, String runMode )
-    {
-        StringBuilder s = new StringBuilder( 128 )
-            .append( ':' )
-            .append( MAGIC_NUMBER )
-            .append( ':' )
-            .append( operation )
-            .append( ':' );
-
-        return runMode == null ? s : s.append( runMode ).append( ':' );
+    static void encodeOpcode( CharsetEncoder encoder, 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 )
+        {
+            return null;
+        }
+
         return trimStackTraces ? stw.writeTrimmedTraceToString() : stw.writeTraceToString();
     }
 
-    static String toBase64( String msg )
+    static String nonNull( String msg )
     {
-        return msg == null ? "-" : new String( BASE64.encode( msg.getBytes( STRING_ENCODING ) ), STREAM_ENCODING );
+        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;
+        final String smartTrimmedStackTrace;
+        final String stackTrace;
+
+        StackTrace( StackTraceWriter stackTraceWriter, boolean trimStackTraces )
+        {
+            SafeThrowable throwable = stackTraceWriter == null ? null : stackTraceWriter.getThrowable();
+            message = throwable == null ? null : throwable.getLocalizedMessage();
+            smartTrimmedStackTrace = stackTraceWriter == null ? null : stackTraceWriter.smartTrimmedStackTrace();
+            stackTrace = stackTraceWriter == null ? null : toStackTrace( stackTraceWriter, trimStackTraces );
+        }
     }
 }
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/LegacyMasterProcessChannelEncoderTest.java
index 4e51318..7f72ba2 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/LegacyMasterProcessChannelEncoderTest.java
@@ -19,6 +19,7 @@ package org.apache.maven.surefire.booter.spi;
  * under the License.
  */
 
+import org.apache.maven.plugin.surefire.log.api.ConsoleLoggerUtils;
 import org.apache.maven.surefire.api.report.ReportEntry;
 import org.apache.maven.surefire.api.report.SafeThrowable;
 import org.apache.maven.surefire.api.report.StackTraceWriter;
@@ -33,20 +34,40 @@ 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.shared.codec.binary.Base64.encodeBase64String;
 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.toBase64;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_SYSPROPS;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.MAGIC_NUMBER;
-import static org.apache.maven.surefire.api.report.RunMode.NORMAL_RUN;
+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;
@@ -57,109 +78,109 @@ import static org.mockito.Mockito.when;
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
  * @since 3.0.0-M4
  */
+@SuppressWarnings( { "checkstyle:linelength", "checkstyle:magicnumber" } )
 public class LegacyMasterProcessChannelEncoderTest
 {
     private static final int ELAPSED_TIME = 102;
+    private static final byte[] ELAPSED_TIME_HEXA = new byte[] {0, 0, 0, 0x66};
 
     @Test
-    public void shouldBeFailSafe()
+    public void shouldComputeStreamPreemptiveLength()
     {
-        assertThat( toBase64( null ) ).isEqualTo( "-" );
-        assertThat( toBase64( "" ) ).isEqualTo( "" );
-    }
+        CharsetEncoder encoder = UTF_8.newEncoder();
 
-    @Test
-    public void shouldHaveSystemProperty()
-    {
-        StringBuilder actualEncoded = encode( BOOTERCODE_SYSPROPS, NORMAL_RUN, "arg1", "arg2" );
-        String expected = ':' + MAGIC_NUMBER + ':' + BOOTERCODE_SYSPROPS.getOpcode()
-            + ":normal-run:UTF-8:YXJnMQ==:YXJnMg==:";
+        // :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 );
 
-        assertThat( actualEncoded.toString() )
-                .isEqualTo( expected );
-    }
+        // :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 );
 
-    @Test
-    public void safeThrowableShouldBeEncoded()
-    {
-        final String exceptionMessage = "msg";
-        final String encodedExceptionMsg = encodeBase64String( toArray( UTF_8.encode( exceptionMessage ) ) );
+        // :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 );
 
-        final String smartStackTrace = "MyTest:86 >> Error";
-        final String encodedSmartStackTrace = encodeBase64String( toArray( UTF_8.encode( smartStackTrace ) ) );
+        // :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 );
 
-        final String stackTrace = "trace line 1\ntrace line 2";
-        final String encodedStackTrace = encodeBase64String( toArray( UTF_8.encode( stackTrace ) ) );
+        // :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 );
 
-        final String trimmedStackTrace = "trace line 1\ntrace line 2";
-        final String encodedTrimmedStackTrace = encodeBase64String( toArray( UTF_8.encode( trimmedStackTrace ) ) );
+        // :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 );
 
-        SafeThrowable safeThrowable = new SafeThrowable( new Exception( 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 );
+        // :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 );
 
-        StringBuilder encoded = new StringBuilder();
-        encode( encoded, stackTraceWriter, false );
-        assertThat( encoded.toString() )
-                .isEqualTo( encodedExceptionMsg
-                    + ":" + encodedSmartStackTrace + ":" + encodedStackTrace + ":" );
-
-        encoded = new StringBuilder();
-        encode( encoded, stackTraceWriter, true );
-        assertThat( encoded.toString() )
-                .isEqualTo( encodedExceptionMsg
-                    + ":" + encodedSmartStackTrace + ":" + encodedTrimmedStackTrace + ":" );
-    }
+        // :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 );
 
-    @Test
-    public void emptySafeThrowable()
-    {
-        SafeThrowable safeThrowable = new SafeThrowable( new Exception( "" ) );
+        // :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 );
 
-        StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
-        when( stackTraceWriter.getThrowable() ).thenReturn( safeThrowable );
-        when( stackTraceWriter.smartTrimmedStackTrace() ).thenReturn( "" );
-        when( stackTraceWriter.writeTraceToString() ).thenReturn( "" );
+        // :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 );
 
-        StringBuilder encoded = new StringBuilder();
-        encode( encoded, stackTraceWriter, false );
+        // :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 );
 
-        assertThat( encoded.toString() )
-                .isEqualTo( ":::" );
-    }
+        // :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 );
 
-    @Test
-    public void nullSafeThrowable()
-    {
-        SafeThrowable safeThrowable = new SafeThrowable( new Exception() );
+        // :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 );
 
-        StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
-        when( stackTraceWriter.getThrowable() ).thenReturn( safeThrowable );
+        // :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 );
 
-        StringBuilder encoded = new StringBuilder();
-        encode( encoded, stackTraceWriter, false );
+        // :maven-surefire-event:17:stop-on-next-test:
+        assertThat( estimateBufferLength( BOOTERCODE_STOP_ON_NEXT_TEST, null, null, 0 ) )
+            .isEqualTo( 42 );
 
-        assertThat( encoded.toString() )
-                .isEqualTo( "-:-:-:" );
+        // :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";
-        final String encodedExceptionMsg = encodeBase64String( toArray( UTF_8.encode( exceptionMessage ) ) );
-
         final String smartStackTrace = "MyTest:86 >> Error";
-        final String encodedSmartStackTrace = encodeBase64String( toArray( UTF_8.encode( smartStackTrace ) ) );
-
         final String stackTrace = "trace line 1\ntrace line 2";
-        final String encodedStackTrace = encodeBase64String( toArray( UTF_8.encode( stackTrace ) ) );
-
         final String trimmedStackTrace = "trace line 1\ntrace line 2";
-        final String encodedTrimmedStackTrace = encodeBase64String( toArray( UTF_8.encode( trimmedStackTrace ) ) );
 
         SafeThrowable safeThrowable = new SafeThrowable( new Exception( exceptionMessage ) );
         StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
@@ -168,7 +189,6 @@ public class LegacyMasterProcessChannelEncoderTest
         when( stackTraceWriter.writeTrimmedTraceToString() ).thenReturn( trimmedStackTrace );
         when( stackTraceWriter.writeTraceToString() ).thenReturn( stackTrace );
 
-
         ReportEntry reportEntry = mock( ReportEntry.class );
         when( reportEntry.getElapsed() ).thenReturn( ELAPSED_TIME );
         when( reportEntry.getGroup() ).thenReturn( "this group" );
@@ -178,140 +198,228 @@ public class LegacyMasterProcessChannelEncoderTest
         when( reportEntry.getSourceName() ).thenReturn( "pkg.MyTest" );
         when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
-        String encodedSourceName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getSourceName() ) ) );
-        String encodedName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getName() ) ) );
-        String encodedGroup = encodeBase64String( toArray( UTF_8.encode( reportEntry.getGroup() ) ) );
-        String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
-
-        StringBuilder encode = encode( "X", "normal-run", reportEntry, false );
-        assertThat( encode.toString() )
-                .isEqualTo( ":maven-surefire-event:X:normal-run:UTF-8:"
-                                + encodedSourceName
-                                + ":"
-                                + "-"
-                                + ":"
-                                + encodedName
-                                + ":"
-                                + "-"
-                                + ":"
-                                + encodedGroup
-                                + ":"
-                                + encodedMessage
-                                + ":"
-                                + ELAPSED_TIME
-                                + ":"
-
-                                + encodedExceptionMsg
-                                + ":"
-                                + encodedSmartStackTrace
-                                + ":"
-                                + encodedStackTrace
-                                + ":"
-                );
-
-        encode = encode( "X", "normal-run", reportEntry, true );
-        assertThat( encode.toString() )
-                .isEqualTo( ":maven-surefire-event:X:normal-run:UTF-8:"
-                                + encodedSourceName
-                                + ":"
-                                + "-"
-                                + ":"
-                                + encodedName
-                                + ":"
-                                + "-"
-                                + ":"
-                                + encodedGroup
-                                + ":"
-                                + encodedMessage
-                                + ":"
-                                + ELAPSED_TIME
-                                + ":"
-
-                                + encodedExceptionMsg
-                                + ":"
-                                + encodedSmartStackTrace
-                                + ":"
-                                + encodedTrimmedStackTrace
-                                + ":"
-                );
+        ByteBuffer encoded = encode( BOOTERCODE_TEST_ERROR, NORMAL_RUN, reportEntry, false );
+        ByteArrayOutputStream expectedFrame = new ByteArrayOutputStream();
+        expectedFrame.write( ":maven-surefire-event:".getBytes( UTF_8 ) );
+        expectedFrame.write( (byte) 10 );
+        expectedFrame.write( ":test-error:".getBytes( UTF_8 ) );
+        expectedFrame.write( (byte) 10 );
+        expectedFrame.write( ":normal-run:".getBytes( UTF_8 ) );
+        expectedFrame.write( (byte) 5 );
+        expectedFrame.write( ":UTF-8:".getBytes( UTF_8 ) );
+        expectedFrame.write( new byte[] {0, 0, 0, 10} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getSourceName().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 1} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0 );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 7} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getName().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 1} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0 );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 10} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getGroup().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 12} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getMessage().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0xff );
+        expectedFrame.write( ELAPSED_TIME_HEXA );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 3} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( exceptionMessage.getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 18} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getStackTraceWriter().smartTrimmedStackTrace().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 25} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( stackTrace.getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        encoded.flip();
+
+        assertThat( toArray( encoded ) )
+            .isEqualTo( expectedFrame.toByteArray() );
+
+        encoded = encode( BOOTERCODE_TEST_ERROR, NORMAL_RUN, reportEntry, true );
+        expectedFrame = new ByteArrayOutputStream();
+        expectedFrame.write( ":maven-surefire-event:".getBytes( UTF_8 ) );
+        expectedFrame.write( (byte) 10 );
+        expectedFrame.write( ":test-error:".getBytes( UTF_8 ) );
+        expectedFrame.write( (byte) 10 );
+        expectedFrame.write( ":normal-run:".getBytes( UTF_8 ) );
+        expectedFrame.write( (byte) 5 );
+        expectedFrame.write( ":UTF-8:".getBytes( UTF_8 ) );
+        expectedFrame.write( new byte[] {0, 0, 0, 10} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getSourceName().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 1} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0 );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 7} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getName().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 1} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0 );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 10} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getGroup().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 12} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getMessage().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0xff );
+        expectedFrame.write( ELAPSED_TIME_HEXA );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 3} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( exceptionMessage.getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 18} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getStackTraceWriter().smartTrimmedStackTrace().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 25} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( trimmedStackTrace.getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        encoded.flip();
+
+        assertThat( toArray( encoded ) )
+            .isEqualTo( expectedFrame.toByteArray() );
 
         Stream out = Stream.newStream();
         LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testSetStarting( reportEntry, true );
-        LineNumberReader printedLines = out.newReader( UTF_8 );
-        assertThat( printedLines.readLine() )
-                .isEqualTo( ":maven-surefire-event:testset-starting:normal-run:UTF-8:"
-                                    + encodedSourceName
-                                    + ":"
-                                    + "-"
-                                    + ":"
-                                    + encodedName
-                                    + ":"
-                                    + "-"
-                                    + ":"
-                                    + encodedGroup
-                                    + ":"
-                                    + encodedMessage
-                                    + ":"
-                                    + ELAPSED_TIME
-                                    + ":"
-
-                                    + encodedExceptionMsg
-                                    + ":"
-                                    + encodedSmartStackTrace
-                                    + ":"
-                                    + encodedTrimmedStackTrace
-                                    + ":"
-                );
-        assertThat( printedLines.readLine() ).isNull();
+        expectedFrame = new ByteArrayOutputStream();
+        expectedFrame.write( ":maven-surefire-event:".getBytes( UTF_8 ) );
+        expectedFrame.write( (byte) 16 );
+        expectedFrame.write( ":testset-starting:".getBytes( UTF_8 ) );
+        expectedFrame.write( (byte) 10 );
+        expectedFrame.write( ":normal-run:".getBytes( UTF_8 ) );
+        expectedFrame.write( (byte) 5 );
+        expectedFrame.write( ":UTF-8:".getBytes( UTF_8 ) );
+        expectedFrame.write( new byte[] {0, 0, 0, 10} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getSourceName().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 1} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0 );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 7} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getName().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 1} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0 );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 10} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getGroup().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 12} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getMessage().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0xff );
+        expectedFrame.write( ELAPSED_TIME_HEXA );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 3} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( exceptionMessage.getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 18} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getStackTraceWriter().smartTrimmedStackTrace().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 25} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( trimmedStackTrace.getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        assertThat( out.toByteArray() )
+            .isEqualTo( expectedFrame.toByteArray() );
 
         out = Stream.newStream();
         encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testSetStarting( reportEntry, false );
-        printedLines = out.newReader( UTF_8 );
-        assertThat( printedLines.readLine() )
-                .isEqualTo( ":maven-surefire-event:testset-starting:normal-run:UTF-8:"
-                                    + encodedSourceName
-                                    + ":"
-                                    + "-"
-                                    + ":"
-                                    + encodedName
-                                    + ":"
-                                    + "-"
-                                    + ":"
-                                    + encodedGroup
-                                    + ":"
-                                    + encodedMessage
-                                    + ":"
-                                    + ELAPSED_TIME
-                                    + ":"
-
-                                    + encodedExceptionMsg
-                                    + ":"
-                                    + encodedSmartStackTrace
-                                    + ":"
-                                    + encodedStackTrace
-                                    + ":"
-                );
-        assertThat( printedLines.readLine() ).isNull();
+        expectedFrame = new ByteArrayOutputStream();
+        expectedFrame.write( ":maven-surefire-event:".getBytes( UTF_8 ) );
+        expectedFrame.write( (byte) 16 );
+        expectedFrame.write( ":testset-starting:".getBytes( UTF_8 ) );
+        expectedFrame.write( (byte) 10 );
+        expectedFrame.write( ":normal-run:".getBytes( UTF_8 ) );
+        expectedFrame.write( (byte) 5 );
+        expectedFrame.write( ":UTF-8:".getBytes( UTF_8 ) );
+        expectedFrame.write( new byte[] {0, 0, 0, 10} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getSourceName().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 1} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0 );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 7} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getName().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 1} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0 );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 10} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getGroup().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 12} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getMessage().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0xff );
+        expectedFrame.write( ELAPSED_TIME_HEXA );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 3} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( exceptionMessage.getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 18} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getStackTraceWriter().smartTrimmedStackTrace().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 25} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( stackTrace.getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        assertThat( out.toByteArray() )
+            .isEqualTo( expectedFrame.toByteArray() );
     }
 
     @Test
     public void testSetCompleted() throws IOException
     {
         String exceptionMessage = "msg";
-        String encodedExceptionMsg = encodeBase64String( toArray( UTF_8.encode( exceptionMessage ) ) );
-
         String smartStackTrace = "MyTest:86 >> Error";
-        String encodedSmartStackTrace = encodeBase64String( toArray( UTF_8.encode( smartStackTrace ) ) );
-
         String stackTrace = "trace line 1\ntrace line 2";
-        String encodedStackTrace = encodeBase64String( toArray( UTF_8.encode( stackTrace ) ) );
-
         String trimmedStackTrace = "trace line 1\ntrace line 2";
-        String encodedTrimmedStackTrace = encodeBase64String( toArray( UTF_8.encode( trimmedStackTrace ) ) );
 
         SafeThrowable safeThrowable = new SafeThrowable( new Exception( exceptionMessage ) );
         StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
@@ -329,57 +437,68 @@ public class LegacyMasterProcessChannelEncoderTest
         when( reportEntry.getSourceName() ).thenReturn( "pkg.MyTest" );
         when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
-        String encodedSourceName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getSourceName() ) ) );
-        String encodedName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getName() ) ) );
-        String encodedGroup = encodeBase64String( toArray( UTF_8.encode( reportEntry.getGroup() ) ) );
-        String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
-
         Stream out = Stream.newStream();
         LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testSetCompleted( reportEntry, false );
-        LineNumberReader printedLines = out.newReader( UTF_8 );
-        assertThat( printedLines.readLine() )
-                .isEqualTo( ":maven-surefire-event:testset-completed:normal-run:UTF-8:"
-                        + encodedSourceName
-                        + ":"
-                        + "-"
-                        + ":"
-                        + encodedName
-                        + ":"
-                        + "-"
-                        + ":"
-                        + encodedGroup
-                        + ":"
-                        + encodedMessage
-                        + ":"
-                        + ELAPSED_TIME
-                        + ":"
-
-                        + encodedExceptionMsg
-                        + ":"
-                        + encodedSmartStackTrace
-                        + ":"
-                        + encodedStackTrace
-                        + ":"
-                );
-        assertThat( printedLines.readLine() ).isNull();
+        ByteArrayOutputStream expectedFrame = new ByteArrayOutputStream();
+        expectedFrame.write( ":maven-surefire-event:".getBytes( UTF_8 ) );
+        expectedFrame.write( (byte) 17 );
+        expectedFrame.write( ":testset-completed:".getBytes( UTF_8 ) );
+        expectedFrame.write( (byte) 10 );
+        expectedFrame.write( ":normal-run:".getBytes( UTF_8 ) );
+        expectedFrame.write( (byte) 5 );
+        expectedFrame.write( ":UTF-8:".getBytes( UTF_8 ) );
+        expectedFrame.write( new byte[] {0, 0, 0, 10} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getSourceName().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 1} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0 );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 7} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getName().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 1} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0 );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 10} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getGroup().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 12} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getMessage().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0xff );
+        expectedFrame.write( ELAPSED_TIME_HEXA );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 3} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( exceptionMessage.getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 18} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getStackTraceWriter().smartTrimmedStackTrace().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 25} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( stackTrace.getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        assertThat( out.toByteArray() )
+            .isEqualTo( expectedFrame.toByteArray() );
     }
 
     @Test
     public void testStarting() throws IOException
     {
         String exceptionMessage = "msg";
-        String encodedExceptionMsg = encodeBase64String( toArray( UTF_8.encode( exceptionMessage ) ) );
-
         String smartStackTrace = "MyTest:86 >> Error";
-        String encodedSmartStackTrace = encodeBase64String( toArray( UTF_8.encode( smartStackTrace ) ) );
-
         String stackTrace = "trace line 1\ntrace line 2";
-        String encodedStackTrace = encodeBase64String( toArray( UTF_8.encode( stackTrace ) ) );
-
         String trimmedStackTrace = "trace line 1\ntrace line 2";
-        String encodedTrimmedStackTrace = encodeBase64String( toArray( UTF_8.encode( trimmedStackTrace ) ) );
 
         SafeThrowable safeThrowable = new SafeThrowable( new Exception( exceptionMessage ) );
         StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
@@ -397,57 +516,69 @@ public class LegacyMasterProcessChannelEncoderTest
         when( reportEntry.getSourceName() ).thenReturn( "pkg.MyTest" );
         when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
-        String encodedSourceName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getSourceName() ) ) );
-        String encodedName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getName() ) ) );
-        String encodedGroup = encodeBase64String( toArray( UTF_8.encode( reportEntry.getGroup() ) ) );
-        String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
-
         Stream out = Stream.newStream();
         LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testStarting( reportEntry, true );
-        LineNumberReader printedLines = out.newReader( UTF_8 );
-        assertThat( printedLines.readLine() )
-                .isEqualTo( ":maven-surefire-event:test-starting:normal-run:UTF-8:"
-                        + encodedSourceName
-                        + ":"
-                        + "-"
-                        + ":"
-                        + encodedName
-                        + ":"
-                        + "-"
-                        + ":"
-                        + encodedGroup
-                        + ":"
-                        + encodedMessage
-                        + ":"
-                        + ELAPSED_TIME
-                        + ":"
-
-                        + encodedExceptionMsg
-                        + ":"
-                        + encodedSmartStackTrace
-                        + ":"
-                        + encodedTrimmedStackTrace
-                        + ":"
-                );
-        assertThat( printedLines.readLine() ).isNull();
+
+        ByteArrayOutputStream expectedFrame = new ByteArrayOutputStream();
+        expectedFrame.write( ":maven-surefire-event:".getBytes( UTF_8 ) );
+        expectedFrame.write( (byte) 13 );
+        expectedFrame.write( ":test-starting:".getBytes( UTF_8 ) );
+        expectedFrame.write( (byte) 10 );
+        expectedFrame.write( ":normal-run:".getBytes( UTF_8 ) );
+        expectedFrame.write( (byte) 5 );
+        expectedFrame.write( ":UTF-8:".getBytes( UTF_8 ) );
+        expectedFrame.write( new byte[] {0, 0, 0, 10} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getSourceName().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 1} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0 );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 7} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getName().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 1} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0 );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 10} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getGroup().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 12} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getMessage().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0xff );
+        expectedFrame.write( ELAPSED_TIME_HEXA );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 3} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( exceptionMessage.getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 18} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getStackTraceWriter().smartTrimmedStackTrace().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 25} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( stackTrace.getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        assertThat( out.toByteArray() )
+            .isEqualTo( expectedFrame.toByteArray() );
     }
 
     @Test
     public void testSuccess() throws IOException
     {
         String exceptionMessage = "msg";
-        String encodedExceptionMsg = encodeBase64String( toArray( UTF_8.encode( exceptionMessage ) ) );
-
         String smartStackTrace = "MyTest:86 >> Error";
-        String encodedSmartStackTrace = encodeBase64String( toArray( UTF_8.encode( smartStackTrace ) ) );
-
         String stackTrace = "trace line 1\ntrace line 2";
-        String encodedStackTrace = encodeBase64String( toArray( UTF_8.encode( stackTrace ) ) );
-
         String trimmedStackTrace = "trace line 1\ntrace line 2";
-        String encodedTrimmedStackTrace = encodeBase64String( toArray( UTF_8.encode( trimmedStackTrace ) ) );
 
         SafeThrowable safeThrowable = new SafeThrowable( new Exception( exceptionMessage ) );
         StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
@@ -465,57 +596,68 @@ public class LegacyMasterProcessChannelEncoderTest
         when( reportEntry.getSourceName() ).thenReturn( "pkg.MyTest" );
         when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
-        String encodedSourceName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getSourceName() ) ) );
-        String encodedName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getName() ) ) );
-        String encodedGroup = encodeBase64String( toArray( UTF_8.encode( reportEntry.getGroup() ) ) );
-        String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
-
         Stream out = Stream.newStream();
         LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testSucceeded( reportEntry, true );
-        LineNumberReader printedLines = out.newReader( UTF_8 );
-        assertThat( printedLines.readLine() )
-                .isEqualTo( ":maven-surefire-event:test-succeeded:normal-run:UTF-8:"
-                        + encodedSourceName
-                        + ":"
-                        + "-"
-                        + ":"
-                        + encodedName
-                        + ":"
-                        + "-"
-                        + ":"
-                        + encodedGroup
-                        + ":"
-                        + encodedMessage
-                        + ":"
-                        + ELAPSED_TIME
-                        + ":"
-
-                        + encodedExceptionMsg
-                        + ":"
-                        + encodedSmartStackTrace
-                        + ":"
-                        + encodedTrimmedStackTrace
-                        + ":"
-                );
-        assertThat( printedLines.readLine() ).isNull();
+        ByteArrayOutputStream expectedFrame = new ByteArrayOutputStream();
+        expectedFrame.write( ":maven-surefire-event:".getBytes( UTF_8 ) );
+        expectedFrame.write( (byte) 14 );
+        expectedFrame.write( ":test-succeeded:".getBytes( UTF_8 ) );
+        expectedFrame.write( (byte) 10 );
+        expectedFrame.write( ":normal-run:".getBytes( UTF_8 ) );
+        expectedFrame.write( (byte) 5 );
+        expectedFrame.write( ":UTF-8:".getBytes( UTF_8 ) );
+        expectedFrame.write( new byte[] {0, 0, 0, 10} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getSourceName().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 1} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0 );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 7} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getName().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 1} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0 );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 10} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getGroup().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 12} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getMessage().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0xff );
+        expectedFrame.write( ELAPSED_TIME_HEXA );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 3} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( exceptionMessage.getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 18} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getStackTraceWriter().smartTrimmedStackTrace().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 25} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( stackTrace.getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        assertThat( out.toByteArray() )
+            .isEqualTo( expectedFrame.toByteArray() );
     }
 
     @Test
     public void testFailed() throws IOException
     {
         String exceptionMessage = "msg";
-        String encodedExceptionMsg = encodeBase64String( toArray( UTF_8.encode( exceptionMessage ) ) );
-
         String smartStackTrace = "MyTest:86 >> Error";
-        String encodedSmartStackTrace = encodeBase64String( toArray( UTF_8.encode( smartStackTrace ) ) );
-
         String stackTrace = "trace line 1\ntrace line 2";
-        String encodedStackTrace = encodeBase64String( toArray( UTF_8.encode( stackTrace ) ) );
-
         String trimmedStackTrace = "trace line 1\ntrace line 2";
-        String encodedTrimmedStackTrace = encodeBase64String( toArray( UTF_8.encode( trimmedStackTrace ) ) );
 
         SafeThrowable safeThrowable = new SafeThrowable( new Exception( exceptionMessage ) );
         StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
@@ -533,56 +675,67 @@ public class LegacyMasterProcessChannelEncoderTest
         when( reportEntry.getSourceName() ).thenReturn( "pkg.MyTest" );
         when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
-        String encodedSourceName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getSourceName() ) ) );
-        String encodedName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getName() ) ) );
-        String encodedGroup = encodeBase64String( toArray( UTF_8.encode( reportEntry.getGroup() ) ) );
-        String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
-
         Stream out = Stream.newStream();
         LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testFailed( reportEntry, false );
-        LineNumberReader printedLines = out.newReader( UTF_8 );
-        assertThat( printedLines.readLine() )
-                .isEqualTo( ":maven-surefire-event:test-failed:normal-run:UTF-8:"
-                        + encodedSourceName
-                        + ":"
-                        + "-"
-                        + ":"
-                        + encodedName
-                        + ":"
-                        + "-"
-                        + ":"
-                        + encodedGroup
-                        + ":"
-                        + encodedMessage
-                        + ":"
-                        + ELAPSED_TIME
-                        + ":"
-
-                        + encodedExceptionMsg
-                        + ":"
-                        + encodedSmartStackTrace
-                        + ":"
-                        + encodedStackTrace
-                        + ":"
-                );
-        assertThat( printedLines.readLine() ).isNull();
+        ByteArrayOutputStream expectedFrame = new ByteArrayOutputStream();
+        expectedFrame.write( ":maven-surefire-event:".getBytes( UTF_8 ) );
+        expectedFrame.write( (byte) 11 );
+        expectedFrame.write( ":test-failed:".getBytes( UTF_8 ) );
+        expectedFrame.write( (byte) 10 );
+        expectedFrame.write( ":normal-run:".getBytes( UTF_8 ) );
+        expectedFrame.write( (byte) 5 );
+        expectedFrame.write( ":UTF-8:".getBytes( UTF_8 ) );
+        expectedFrame.write( new byte[] {0, 0, 0, 10} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getSourceName().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 1} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0 );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 7} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getName().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 1} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0 );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 10} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getGroup().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 12} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getMessage().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0xff );
+        expectedFrame.write( ELAPSED_TIME_HEXA );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 3} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( exceptionMessage.getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 18} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getStackTraceWriter().smartTrimmedStackTrace().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 25} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( stackTrace.getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        assertThat( out.toByteArray() )
+            .isEqualTo( expectedFrame.toByteArray() );
     }
 
     @Test
     public void testSkipped() throws IOException
     {
-        String encodedExceptionMsg = "-";
-
         String smartStackTrace = "MyTest:86 >> Error";
-        String encodedSmartStackTrace = encodeBase64String( toArray( UTF_8.encode( smartStackTrace ) ) );
-
         String stackTrace = "trace line 1\ntrace line 2";
-        String encodedStackTrace = encodeBase64String( toArray( UTF_8.encode( stackTrace ) ) );
-
         String trimmedStackTrace = "trace line 1\ntrace line 2";
-        String encodedTrimmedStackTrace = encodeBase64String( toArray( UTF_8.encode( trimmedStackTrace ) ) );
 
         SafeThrowable safeThrowable = new SafeThrowable( new Exception() );
         StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
@@ -600,55 +753,67 @@ public class LegacyMasterProcessChannelEncoderTest
         when( reportEntry.getSourceName() ).thenReturn( "pkg.MyTest" );
         when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
-        String encodedSourceName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getSourceName() ) ) );
-        String encodedName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getName() ) ) );
-        String encodedGroup = encodeBase64String( toArray( UTF_8.encode( reportEntry.getGroup() ) ) );
-        String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
-
         Stream out = Stream.newStream();
         LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testSkipped( reportEntry, false );
-        LineNumberReader printedLines = out.newReader( UTF_8 );
-        assertThat( printedLines.readLine() )
-                .isEqualTo( ":maven-surefire-event:test-skipped:normal-run:UTF-8:"
-                        + encodedSourceName
-                        + ":"
-                        + "-"
-                        + ":"
-                        + encodedName
-                        + ":"
-                        + "-"
-                        + ":"
-                        + encodedGroup
-                        + ":"
-                        + encodedMessage
-                        + ":"
-                        + ELAPSED_TIME
-                        + ":"
-
-                        + encodedExceptionMsg
-                        + ":"
-                        + encodedSmartStackTrace
-                        + ":"
-                        + encodedStackTrace
-                        + ":"
-                );
-        assertThat( printedLines.readLine() ).isNull();
+        ByteArrayOutputStream expectedFrame = new ByteArrayOutputStream();
+        expectedFrame.write( ":maven-surefire-event:".getBytes( UTF_8 ) );
+        expectedFrame.write( (byte) 12 );
+        expectedFrame.write( ":test-skipped:".getBytes( UTF_8 ) );
+        expectedFrame.write( (byte) 10 );
+        expectedFrame.write( ":normal-run:".getBytes( UTF_8 ) );
+        expectedFrame.write( (byte) 5 );
+        expectedFrame.write( ":UTF-8:".getBytes( UTF_8 ) );
+        expectedFrame.write( new byte[] {0, 0, 0, 10} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getSourceName().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 1} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0 );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 7} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getName().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 1} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0 );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 10} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getGroup().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 12} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getMessage().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0xFF );
+        expectedFrame.write( ELAPSED_TIME_HEXA );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 1} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0 );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 18} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getStackTraceWriter().smartTrimmedStackTrace().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 25} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( stackTrace.getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        assertThat( out.toByteArray() )
+            .isEqualTo( expectedFrame.toByteArray() );
     }
 
     @Test
     public void testError() throws IOException
     {
-        String encodedExceptionMsg = "-";
-
-        String encodedSmartStackTrace = "-";
-
         String stackTrace = "trace line 1\ntrace line 2";
-        String encodedStackTrace = encodeBase64String( toArray( UTF_8.encode( stackTrace ) ) );
 
-        String trimmedStackTrace = "trace line 1\ntrace line 2";
-        String encodedTrimmedStackTrace = encodeBase64String( toArray( UTF_8.encode( trimmedStackTrace ) ) );
+        String trimmedStackTrace = "trace line 1";
 
         SafeThrowable safeThrowable = new SafeThrowable( new Exception() );
         StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
@@ -666,53 +831,66 @@ public class LegacyMasterProcessChannelEncoderTest
         when( reportEntry.getSourceName() ).thenReturn( "pkg.MyTest" );
         when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
-        String encodedSourceName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getSourceName() ) ) );
-        String encodedName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getName() ) ) );
-        String encodedGroup = encodeBase64String( toArray( UTF_8.encode( reportEntry.getGroup() ) ) );
-        String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
-
         Stream out = Stream.newStream();
         LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
-
         encoder.testError( reportEntry, false );
-        LineNumberReader printedLines = out.newReader( UTF_8 );
-        assertThat( printedLines.readLine() )
-                .isEqualTo( ":maven-surefire-event:test-error:normal-run:UTF-8:"
-                        + encodedSourceName
-                        + ":"
-                        + "-"
-                        + ":"
-                        + encodedName
-                        + ":"
-                        + "-"
-                        + ":"
-                        + encodedGroup
-                        + ":"
-                        + encodedMessage
-                        + ":"
-                        + ELAPSED_TIME
-                        + ":"
-
-                        + encodedExceptionMsg
-                        + ":"
-                        + encodedSmartStackTrace
-                        + ":"
-                        + encodedStackTrace
-                        + ":"
-                );
-        assertThat( printedLines.readLine() ).isNull();
+        ByteArrayOutputStream expectedFrame = new ByteArrayOutputStream();
+        expectedFrame.write( ":maven-surefire-event:".getBytes( UTF_8 ) );
+        expectedFrame.write( (byte) 10 );
+        expectedFrame.write( ":test-error:".getBytes( UTF_8 ) );
+        expectedFrame.write( (byte) 10 );
+        expectedFrame.write( ":normal-run:".getBytes( UTF_8 ) );
+        expectedFrame.write( (byte) 5 );
+        expectedFrame.write( ":UTF-8:".getBytes( UTF_8 ) );
+        expectedFrame.write( new byte[] {0, 0, 0, 10} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getSourceName().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 1} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0 );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 7} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getName().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 1} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0 );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 10} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getGroup().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 12} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getMessage().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0xff );
+        expectedFrame.write( ELAPSED_TIME_HEXA );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 1} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0 );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 1} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0 );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 25} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( stackTrace.getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        assertThat( out.toByteArray() )
+            .isEqualTo( expectedFrame.toByteArray() );
     }
 
     @Test
     public void testAssumptionFailure() throws IOException
     {
         String exceptionMessage = "msg";
-        String encodedExceptionMsg = encodeBase64String( toArray( UTF_8.encode( exceptionMessage ) ) );
 
         String smartStackTrace = "MyTest:86 >> Error";
-        String encodedSmartStackTrace = encodeBase64String( toArray( UTF_8.encode( smartStackTrace ) ) );
-
-        String encodedStackTrace = "-";
 
         SafeThrowable safeThrowable = new SafeThrowable( new Exception( exceptionMessage ) );
         StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
@@ -730,106 +908,131 @@ public class LegacyMasterProcessChannelEncoderTest
         when( reportEntry.getSourceName() ).thenReturn( "pkg.MyTest" );
         when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
-        String encodedSourceName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getSourceName() ) ) );
-        String encodedName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getName() ) ) );
-        String encodedGroup = encodeBase64String( toArray( UTF_8.encode( reportEntry.getGroup() ) ) );
-        String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
-
         Stream out = Stream.newStream();
         LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testAssumptionFailure( reportEntry, false );
-        LineNumberReader printedLines = out.newReader( UTF_8 );
-        assertThat( printedLines.readLine() )
-                .isEqualTo( ":maven-surefire-event:test-assumption-failure:normal-run:UTF-8:"
-                        + encodedSourceName
-                        + ":"
-                        + "-"
-                        + ":"
-                        + encodedName
-                        + ":"
-                        + "-"
-                        + ":"
-                        + encodedGroup
-                        + ":"
-                        + encodedMessage
-                        + ":"
-                        + "-"
-                        + ":"
-
-                        + encodedExceptionMsg
-                        + ":"
-                        + encodedSmartStackTrace
-                        + ":"
-                        + encodedStackTrace
-                        + ":"
-                );
-        assertThat( printedLines.readLine() ).isNull();
+        ByteArrayOutputStream expectedFrame = new ByteArrayOutputStream();
+        expectedFrame.write( ":maven-surefire-event:".getBytes( UTF_8 ) );
+        expectedFrame.write( (byte) 23 );
+        expectedFrame.write( ":test-assumption-failure:".getBytes( UTF_8 ) );
+        expectedFrame.write( (byte) 10 );
+        expectedFrame.write( ":normal-run:".getBytes( UTF_8 ) );
+        expectedFrame.write( (byte) 5 );
+        expectedFrame.write( ":UTF-8:".getBytes( UTF_8 ) );
+        expectedFrame.write( new byte[] {0, 0, 0, 10} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getSourceName().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 1} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0 );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 7} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getName().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 1} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0 );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 10} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getGroup().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 12} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( reportEntry.getMessage().getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0 );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 3} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( exceptionMessage.getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 18} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( smartStackTrace.getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( new byte[] {0, 0, 0, 1} );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0 );
+        expectedFrame.write( ':' );
+        assertThat( out.toByteArray() )
+                .isEqualTo( expectedFrame.toByteArray() );
     }
 
     @Test
-    public void testBye() throws IOException
+    public void testBye()
     {
         Stream out = Stream.newStream();
         LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.bye();
-        LineNumberReader printedLines = out.newReader( UTF_8 );
-        assertThat( printedLines.readLine() )
-                .isEqualTo( ":maven-surefire-event:bye:" );
-        assertThat( printedLines.readLine() ).isNull();
+
+        String encoded = new String( out.toByteArray(), UTF_8 );
+
+        assertThat( encoded )
+                .isEqualTo( ":maven-surefire-event:\u0003:bye:" );
     }
 
     @Test
-    public void testStopOnNextTest() throws IOException
+    public void testStopOnNextTest()
     {
         Stream out = Stream.newStream();
         LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.stopOnNextTest();
-        LineNumberReader printedLines = out.newReader( UTF_8 );
-        assertThat( printedLines.readLine() )
-                .isEqualTo( ":maven-surefire-event:stop-on-next-test:" );
-        assertThat( printedLines.readLine() ).isNull();
+
+        String encoded = new String( out.toByteArray(), UTF_8 );
+        assertThat( encoded )
+                .isEqualTo( ":maven-surefire-event:\u0011:stop-on-next-test:" );
     }
 
     @Test
-    public void testAcquireNextTest() throws IOException
+    public void testAcquireNextTest()
     {
         Stream out = Stream.newStream();
         LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.acquireNextTest();
-        LineNumberReader printedLines = out.newReader( UTF_8 );
-        assertThat( printedLines.readLine() )
-                .isEqualTo( ":maven-surefire-event:next-test:" );
-        assertThat( printedLines.readLine() ).isNull();
+
+        String encoded = new String( out.toByteArray(), UTF_8 );
+        assertThat( encoded )
+                .isEqualTo( ":maven-surefire-event:\u0009:next-test:" );
     }
 
     @Test
     public void testSendOpcode()
     {
-        StringBuilder encoded = encodeOpcode( "some-opcode", "normal-run" );
-        assertThat( encoded.toString() )
-                .isEqualTo( ":maven-surefire-event:some-opcode:normal-run:" );
-
-        encoded = encodeHeader( "some-opcode", "normal-run" );
-        assertThat( encoded.toString() )
-                .isEqualTo( ":maven-surefire-event:some-opcode:normal-run:UTF-8:" );
-
-        encoded = encodeMessage( "some-opcode", "normal-run", "msg" );
-        assertThat( encoded.toString() )
-                .isEqualTo( ":maven-surefire-event:some-opcode:normal-run:UTF-8:msg:" );
-
-        Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
-        encoded = encoder.print( "some-opcode", "msg" );
-        assertThat( encoded.toString() )
-                .isEqualTo( ":maven-surefire-event:some-opcode:UTF-8:bXNn:" );
-
-        encoded = encoder.print( "some-opcode", new String[] { null } );
-        assertThat( encoded.toString() )
-                .isEqualTo( ":maven-surefire-event:some-opcode:UTF-8:-:" );
+        CharsetEncoder encoder = UTF_8.newEncoder();
+        ByteBuffer result = ByteBuffer.allocate( 128 );
+        encodeOpcode( encoder, 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 );
+        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 );
+        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:" );
     }
 
     @Test
@@ -842,10 +1045,7 @@ public class LegacyMasterProcessChannelEncoderTest
 
         String encoded = new String( out.toByteArray(), UTF_8 );
 
-        String expected = ":maven-surefire-event:console-info-log:UTF-8:"
-                                  + encodeBase64String( toArray( UTF_8.encode( "msg" ) ) )
-                                  + ":"
-                                  + "\n";
+        String expected = ":maven-surefire-event:\u0010:console-info-log:\u0005:UTF-8:\u0000\u0000\u0000\u0003:msg:";
 
         assertThat( encoded )
                 .isEqualTo( expected );
@@ -861,9 +1061,10 @@ public class LegacyMasterProcessChannelEncoderTest
 
         String encoded = new String( out.toByteArray(), UTF_8 );
 
-        String expected = ":maven-surefire-event:console-error-log:UTF-8:"
-                + encodeBase64String( toArray( UTF_8.encode( "msg" ) ) )
-                + ":-:-:\n";
+        String expected = ":maven-surefire-event:\u0011:console-error-log:\u0005:UTF-8:"
+            + "\u0000\u0000\u0000\u0003:msg:"
+            + "\u0000\u0000\u0000\u0001:\u0000:"
+            + "\u0000\u0000\u0000\u0001:\u0000:";
 
         assertThat( encoded )
                 .isEqualTo( expected );
@@ -875,11 +1076,36 @@ public class LegacyMasterProcessChannelEncoderTest
         Stream out = Stream.newStream();
         LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
-        encoder.consoleErrorLog( new Exception( "msg" ) );
-        LineNumberReader printedLines = out.newReader( UTF_8 );
-        assertThat( printedLines.readLine() )
-                .startsWith( ":maven-surefire-event:console-error-log:UTF-8:bXNn:-:" );
-        assertThat( printedLines.readLine() ).isNull();
+        Exception e = new Exception( "msg" );
+        encoder.consoleErrorLog( e );
+        String stackTrace = ConsoleLoggerUtils.toString( e );
+        ByteArrayOutputStream expectedFrame = new ByteArrayOutputStream();
+        expectedFrame.write( ":maven-surefire-event:\u0011:console-error-log:\u0005:UTF-8:".getBytes( UTF_8 ) );
+        expectedFrame.write( 0 );
+        expectedFrame.write( 0 );
+        expectedFrame.write( 0 );
+        expectedFrame.write( 3 );
+        expectedFrame.write( ':' );
+        expectedFrame.write( "msg".getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0 );
+        expectedFrame.write( 0 );
+        expectedFrame.write( 0 );
+        expectedFrame.write( 1 );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0 );
+        expectedFrame.write( ':' );
+        byte[] stackTraceBytes = stackTrace.getBytes( UTF_8 );
+        int[] stackTraceLength = toBytes( stackTraceBytes.length );
+        expectedFrame.write( stackTraceLength[0] );
+        expectedFrame.write( stackTraceLength[1] );
+        expectedFrame.write( stackTraceLength[2] );
+        expectedFrame.write( stackTraceLength[3] );
+        expectedFrame.write( ':' );
+        expectedFrame.write( stackTraceBytes );
+        expectedFrame.write( ':' );
+        assertThat( out.toByteArray() )
+            .isEqualTo( expectedFrame.toByteArray() );
     }
 
     @Test
@@ -888,15 +1114,40 @@ public class LegacyMasterProcessChannelEncoderTest
         Stream out = Stream.newStream();
         LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
-        encoder.consoleErrorLog( "msg2", new Exception( "msg" ) );
-        LineNumberReader printedLines = out.newReader( UTF_8 );
-        assertThat( printedLines.readLine() )
-                .startsWith( ":maven-surefire-event:console-error-log:UTF-8:bXNnMg==:-:" );
-        assertThat( printedLines.readLine() ).isNull();
+        Exception e = new Exception( "msg" );
+        encoder.consoleErrorLog( "msg2", e );
+        String stackTrace = ConsoleLoggerUtils.toString( e );
+        ByteArrayOutputStream expectedFrame = new ByteArrayOutputStream();
+        expectedFrame.write( ":maven-surefire-event:\u0011:console-error-log:\u0005:UTF-8:".getBytes( UTF_8 ) );
+        expectedFrame.write( 0 );
+        expectedFrame.write( 0 );
+        expectedFrame.write( 0 );
+        expectedFrame.write( 4 );
+        expectedFrame.write( ':' );
+        expectedFrame.write( "msg2".getBytes( UTF_8 ) );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0 );
+        expectedFrame.write( 0 );
+        expectedFrame.write( 0 );
+        expectedFrame.write( 1 );
+        expectedFrame.write( ':' );
+        expectedFrame.write( 0 );
+        expectedFrame.write( ':' );
+        byte[] stackTraceBytes = stackTrace.getBytes( UTF_8 );
+        int[] stackTraceLength = toBytes( stackTraceBytes.length );
+        expectedFrame.write( stackTraceLength[0] );
+        expectedFrame.write( stackTraceLength[1] );
+        expectedFrame.write( stackTraceLength[2] );
+        expectedFrame.write( stackTraceLength[3] );
+        expectedFrame.write( ':' );
+        expectedFrame.write( stackTraceBytes );
+        expectedFrame.write( ':' );
+        assertThat( out.toByteArray() )
+            .isEqualTo( expectedFrame.toByteArray() );
     }
 
     @Test
-    public void testConsoleErrorLog3() throws IOException
+    public void testConsoleErrorLog3()
     {
         Stream out = Stream.newStream();
         LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
@@ -908,10 +1159,9 @@ public class LegacyMasterProcessChannelEncoderTest
         when( stackTraceWriter.writeTrimmedTraceToString() ).thenReturn( "4" );
 
         encoder.consoleErrorLog( stackTraceWriter, true );
-        LineNumberReader printedLines = out.newReader( UTF_8 );
-        assertThat( printedLines.readLine() )
-                .startsWith( ":maven-surefire-event:console-error-log:UTF-8:MQ==:Mg==:NA==:" );
-        assertThat( printedLines.readLine() ).isNull();
+        String encoded = new String( out.toByteArray(), UTF_8 );
+        assertThat( encoded )
+                .startsWith( ":maven-surefire-event:\u0011:console-error-log:\u0005:UTF-8:\u0000\u0000\u0000\u0001:1:\u0000\u0000\u0000\u0001:2:\u0000\u0000\u0000\u0001:4:" );
     }
 
     @Test
@@ -924,10 +1174,7 @@ public class LegacyMasterProcessChannelEncoderTest
 
         String encoded = new String( out.toByteArray(), UTF_8 );
 
-        String expected = ":maven-surefire-event:console-debug-log:UTF-8:"
-                                  + encodeBase64String( toArray( UTF_8.encode( "msg" ) ) )
-                                  + ":"
-                                  + "\n";
+        String expected = ":maven-surefire-event:\u0011:console-debug-log:\u0005:UTF-8:\u0000\u0000\u0000\u0003:msg:";
 
         assertThat( encoded )
                 .isEqualTo( expected );
@@ -943,10 +1190,7 @@ public class LegacyMasterProcessChannelEncoderTest
 
         String encoded = new String( out.toByteArray(), UTF_8 );
 
-        String expected = ":maven-surefire-event:console-warning-log:UTF-8:"
-                                  + encodeBase64String( toArray( UTF_8.encode( "msg" ) ) )
-                                  + ":"
-                                  + "\n";
+        String expected = ":maven-surefire-event:\u0013:console-warning-log:\u0005:UTF-8:\u0000\u0000\u0000\u0003:msg:";
 
         assertThat( encoded )
                 .isEqualTo( expected );
@@ -962,13 +1206,11 @@ public class LegacyMasterProcessChannelEncoderTest
         encoder.stdOut( "msg", false );
         channel.close();
 
-        String expected = ":maven-surefire-event:std-out-stream:normal-run:UTF-8:bXNn:";
+        String expected = ":maven-surefire-event:\u000e:std-out-stream:"
+            + (char) 10 + ":normal-run:\u0005:UTF-8:\u0000\u0000\u0000\u0003:msg:";
 
-        LineNumberReader printedLines = out.newReader( UTF_8 );
-        assertThat( printedLines.readLine() )
+        assertThat( new String( out.toByteArray(), UTF_8 ) )
                 .isEqualTo( expected );
-        assertThat( printedLines.readLine() )
-                .isNull();
     }
 
     @Test
@@ -981,13 +1223,11 @@ public class LegacyMasterProcessChannelEncoderTest
         encoder.stdOut( "msg", true );
         channel.close();
 
-        String expected = ":maven-surefire-event:std-out-stream-new-line:normal-run:UTF-8:bXNn:";
+        String expected = ":maven-surefire-event:\u0017:std-out-stream-new-line:"
+            + (char) 10 + ":normal-run:\u0005:UTF-8:\u0000\u0000\u0000\u0003:msg:";
 
-        LineNumberReader printedLines = out.newReader( UTF_8 );
-        assertThat( printedLines.readLine() )
+        assertThat( new String( out.toByteArray(), UTF_8 ) )
                 .isEqualTo( expected );
-        assertThat( printedLines.readLine() )
-                .isNull();
     }
 
     @Test
@@ -1000,13 +1240,11 @@ public class LegacyMasterProcessChannelEncoderTest
         encoder.stdErr( "msg", false );
         channel.close();
 
-        String expected = ":maven-surefire-event:std-err-stream:normal-run:UTF-8:bXNn:";
+        String expected = ":maven-surefire-event:\u000e:std-err-stream:"
+            + (char) 10 + ":normal-run:\u0005:UTF-8:\u0000\u0000\u0000\u0003:msg:";
 
-        LineNumberReader printedLines = out.newReader( UTF_8 );
-        assertThat( printedLines.readLine() )
+        assertThat( new String( out.toByteArray(), UTF_8 ) )
                 .isEqualTo( expected );
-        assertThat( printedLines.readLine() )
-                .isNull();
     }
 
     @Test
@@ -1019,43 +1257,50 @@ public class LegacyMasterProcessChannelEncoderTest
         encoder.stdErr( "msg", true );
         channel.close();
 
-        String expected = ":maven-surefire-event:std-err-stream-new-line:normal-run:UTF-8:bXNn:";
+        String expected = ":maven-surefire-event:\u0017:std-err-stream-new-line:"
+            + (char) 10 + ":normal-run:\u0005:UTF-8:\u0000\u0000\u0000\u0003:msg:";
 
-        LineNumberReader printedLines = out.newReader( UTF_8 );
-        assertThat( printedLines.readLine() )
+        assertThat( new String( out.toByteArray(), UTF_8 ) )
                 .isEqualTo( expected );
-        assertThat( printedLines.readLine() )
-                .isNull();
     }
 
     @Test
     @SuppressWarnings( "checkstyle:innerassignment" )
     public void shouldCountSameNumberOfSystemProperties() throws IOException
     {
-        Stream out = Stream.newStream();
-        WritableBufferedByteChannel channel = newBufferedChannel( out );
+        Stream stream = Stream.newStream();
+        WritableBufferedByteChannel channel = newBufferedChannel( stream );
         LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( channel );
 
         Map<String, String> sysProps = ObjectUtils.systemProps();
-        int expectedSize = sysProps.size();
         encoder.sendSystemProperties( sysProps );
         channel.close();
 
-        LineNumberReader printedLines = out.newReader( UTF_8 );
-
-        int size = 0;
-        for ( String line; ( line = printedLines.readLine() ) != null; size++ )
+        for ( Entry<String, String> entry : sysProps.entrySet() )
         {
-            assertThat( line )
-                    .startsWith( ":maven-surefire-event:sys-prop:normal-run:UTF-8:" );
+            int[] k = toBytes( entry.getKey().length() );
+            int[] v = toBytes( entry.getValue() == null ? 1 : entry.getValue().getBytes( UTF_8 ).length );
+            ByteArrayOutputStream expectedFrame = new ByteArrayOutputStream();
+            expectedFrame.write( ":maven-surefire-event:sys-prop:normal-run:UTF-8:".getBytes( UTF_8 ) );
+            expectedFrame.write( k[0] );
+            expectedFrame.write( k[1] );
+            expectedFrame.write( k[2] );
+            expectedFrame.write( k[3] );
+            expectedFrame.write( ':' );
+            expectedFrame.write( v[0] );
+            expectedFrame.write( v[1] );
+            expectedFrame.write( v[2] );
+            expectedFrame.write( v[3] );
+            expectedFrame.write( ':' );
+            expectedFrame.write( ( entry.getValue() == null ? "\u0000" : entry.getValue() ).getBytes( UTF_8 ) );
+            expectedFrame.write( ':' );
+            assertThat( stream.toByteArray() )
+                .contains( expectedFrame.toByteArray() );
         }
-
-        assertThat( size )
-                .isEqualTo( expectedSize );
     }
 
     @Test
-    public void shouldHandleExit() throws IOException
+    public void shouldHandleExit()
     {
         Stream out = Stream.newStream();
 
@@ -1067,13 +1312,12 @@ public class LegacyMasterProcessChannelEncoderTest
         when( stackTraceWriter.writeTrimmedTraceToString() ).thenReturn( "4" );
         encoder.sendExitError( stackTraceWriter, false );
 
-        LineNumberReader printedLines = out.newReader( UTF_8 );
-        assertThat( printedLines.readLine() )
-                .startsWith( ":maven-surefire-event:jvm-exit-error:UTF-8:MQ==:Mg==:Mw==:" );
+        assertThat( new String( out.toByteArray(), UTF_8 ) )
+                .startsWith( ":maven-surefire-event:\u000e:jvm-exit-error:\u0005:UTF-8:\u0000\u0000\u0000\u0001:1:\u0000\u0000\u0000\u0001:2:\u0000\u0000\u0000\u0001:3:" );
     }
 
     @Test
-    public void shouldHandleExitWithTrimmedTrace() throws IOException
+    public void shouldHandleExitWithTrimmedTrace()
     {
         Stream out = Stream.newStream();
 
@@ -1085,9 +1329,8 @@ public class LegacyMasterProcessChannelEncoderTest
         when( stackTraceWriter.writeTrimmedTraceToString() ).thenReturn( "4" );
         encoder.sendExitError( stackTraceWriter, true );
 
-        LineNumberReader printedLines = out.newReader( UTF_8 );
-        assertThat( printedLines.readLine() )
-                .startsWith( ":maven-surefire-event:jvm-exit-error:UTF-8:MQ==:Mg==:NA==:" );
+        assertThat( new String( out.toByteArray(), UTF_8 ) )
+            .startsWith( ":maven-surefire-event:\u000e:jvm-exit-error:\u0005:UTF-8:\u0000\u0000\u0000\u0001:1:\u0000\u0000\u0000\u0001:2:\u0000\u0000\u0000\u0001:4:" );
     }
 
     @Test
@@ -1110,13 +1353,9 @@ public class LegacyMasterProcessChannelEncoderTest
                     .isTrue();
         }
 
-        String expected = ":maven-surefire-event:std-out-stream:normal-run:UTF-8:bXNn:";
-
-        LineNumberReader printedLines = out.newReader( UTF_8 );
-        assertThat( printedLines.readLine() )
-                .isEqualTo( expected );
-        assertThat( printedLines.readLine() )
-                .isNull();
+        assertThat( new String( out.toByteArray(), UTF_8 ) )
+                .isEqualTo( ":maven-surefire-event:\u000e:std-out-stream:"
+                    + (char) 10 + ":normal-run:\u0005:UTF-8:\u0000\u0000\u0000\u0003:msg:" );
     }
 
     private static class Stream extends PrintStream
@@ -1150,4 +1389,51 @@ public class LegacyMasterProcessChannelEncoderTest
         return copyOfRange( buffer.array(), buffer.arrayOffset(), buffer.arrayOffset() + buffer.remaining() );
     }
 
+    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 int[] toBytes( int i )
+    {
+        int[] result = new int[4];
+        result[0] = 0xff & ( i >> 24 );
+        result[1] = 0xff & ( i >> 16 );
+        result[2] = 0xff & ( i >> 8 );
+        result[3] = 0xff & i;
+        return result;
+    }
+
+    private static final class Channel implements WritableBufferedByteChannel
+    {
+        ByteBuffer src;
+
+        @Override
+        public void writeBuffered( ByteBuffer src ) throws IOException
+        {
+            this.src = src;
+        }
+
+        @Override
+        public int write( ByteBuffer src ) throws IOException
+        {
+            this.src = src;
+            return 0;
+        }
+
+        @Override
+        public boolean isOpen()
+        {
+            return false;
+        }
+
+        @Override
+        public void close()
+        {
+
+        }
+    }
 }
diff --git a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkNodeArguments.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkNodeArguments.java
index 53da2bd..4ab6cc3 100644
--- a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkNodeArguments.java
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkNodeArguments.java
@@ -47,6 +47,9 @@ public interface ForkNodeArguments
     @Nonnull
     File dumpStreamText( @Nonnull String text );
 
+    @Nonnull
+    File dumpStreamException( @Nonnull Throwable t );
+
     void logWarningAtEnd( @Nonnull String text );
 
     @Nonnull
diff --git a/surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire224WellFormedXmlFailuresIT.java b/surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire224WellFormedXmlFailuresIT.java
index b278c7a..1770c16 100644
--- a/surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire224WellFormedXmlFailuresIT.java
+++ b/surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire224WellFormedXmlFailuresIT.java
@@ -73,6 +73,6 @@ public class Surefire224WellFormedXmlFailuresIT
         assertEquals( "Wrong error message", "<", testLower.getFailureMessage() );
         assertEquals( "Wrong error message", ">", testGreater.getFailureMessage() );
         // SUREFIRE-456 we have to doubly-escape non-visible control characters like \u0000
-        assertEquals( "Wrong error message", "&#0;", testU0000.getFailureMessage() );
+        assertEquals( "Wrong error message", "Hi &#0; there!", testU0000.getFailureMessage() );
     }
 }
diff --git a/surefire-its/src/test/resources/surefire-224-wellFormedXmlFailures/src/test/java/wellFormedXmlFailures/TestSurefire3.java b/surefire-its/src/test/resources/surefire-224-wellFormedXmlFailures/src/test/java/wellFormedXmlFailures/TestSurefire3.java
index 7bb1afe..20f2736 100644
--- a/surefire-its/src/test/resources/surefire-224-wellFormedXmlFailures/src/test/java/wellFormedXmlFailures/TestSurefire3.java
+++ b/surefire-its/src/test/resources/surefire-224-wellFormedXmlFailures/src/test/java/wellFormedXmlFailures/TestSurefire3.java
@@ -56,7 +56,7 @@ public class TestSurefire3
 
     public void testU0000()
     {
-        fail( "\u0000" );
+        fail( "Hi \u0000 there!" );
     }
 
 }