/* * Copyright (c) 2002-2017 "Neo Technology," * Network Engine for Objects in Lund AB [http://neotechnology.com] * * This file is part of Neo4j. * * Licensed 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. */ package org.neo4j.driver.v1.util; import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.neo4j.driver.internal.net.ChunkedInput; import org.neo4j.driver.internal.messaging.ResetMessage; import org.neo4j.driver.internal.messaging.AckFailureMessage; import org.neo4j.driver.internal.messaging.DiscardAllMessage; import org.neo4j.driver.internal.messaging.FailureMessage; import org.neo4j.driver.internal.messaging.IgnoredMessage; import org.neo4j.driver.internal.messaging.InitMessage; import org.neo4j.driver.internal.messaging.Message; import org.neo4j.driver.internal.messaging.MessageFormat; import org.neo4j.driver.internal.messaging.MessageHandler; import org.neo4j.driver.internal.messaging.PackStreamMessageFormatV1; import org.neo4j.driver.internal.messaging.PullAllMessage; import org.neo4j.driver.internal.messaging.RecordMessage; import org.neo4j.driver.internal.messaging.RunMessage; import org.neo4j.driver.internal.messaging.SuccessMessage; import org.neo4j.driver.internal.packstream.PackInput; import org.neo4j.driver.internal.util.BytePrinter; import org.neo4j.driver.v1.Value; public class DumpMessage { public static void main( String[] args ) { if ( args.length < 1 ) { System.out.println( "Please specify PackStreamV1 messages " + "(or PackStreamV1 messages in chunks) " + "that you want to unpack in hex strings. " ); return; } StringBuilder hexStr = new StringBuilder(); for ( String arg : args ) { hexStr.append( arg ); } byte[] bytes = BytePrinter.hexStringToBytes( hexStr.toString() ); // for now we only handle PackStreamV1 ArrayList<Message> messages; try { // first try to interpret as a message with chunk header and 00 00 ending messages = unpackPackStreamV1WithHeader( bytes ); } catch ( IOException e ) { // fall back to interpret as a message without chunk header and 00 00 ending try { messages = unpackPackStreamV1Message( bytes ); } catch ( IOException ee ) { // If both of them failed, then print the debug info for both of them. System.err.println( "Failed to interpret the given hex string." ); e.printStackTrace(); ee.printStackTrace(); return; } } for ( Message message : messages ) { System.out.println( message ); } } public static ArrayList<Message> unpackPackStreamV1WithHeader( byte[] bytes ) throws IOException { ArrayList<Message> messages = new ArrayList<>(); ByteArrayChunkedInput chunkedInput = new ByteArrayChunkedInput( bytes ); try { PackStreamMessageFormatV1.Reader reader = new PackStreamMessageFormatV1.Reader( chunkedInput, chunkedInput.messageBoundaryHook() ); unpack( messages, reader ); return messages; } catch ( Exception e ) { int offset = chunkedInput.prePos; throw new IOException( "Error when interpreting the message as PackStreamV1 message with chunk size and 00 00 ending:" + "\nMessage interpreted : " + messages + "\n" + BytePrinter.hexInOneLine( ByteBuffer.wrap( bytes ), 0, bytes.length ) + /* all bytes */ "\n" + padLeft( offset ) /* the indicator of the error place*/, e ); } } public static ArrayList<Message> unpackPackStreamV1Message( byte[] bytes ) throws IOException { ArrayList<Message> messages = new ArrayList<>(); byte[] bytesWithHeadTail = putBytesInOneChunk( bytes ); ByteArrayChunkedInput chunkedInput = new ByteArrayChunkedInput( bytesWithHeadTail ); try { PackStreamMessageFormatV1.Reader reader = new PackStreamMessageFormatV1.Reader( chunkedInput, chunkedInput.messageBoundaryHook() ); unpack( messages, reader ); return messages; } catch ( Exception e ) { int offset = chunkedInput.prePos - 2; // not including the chunk size throw new IOException( "Error when interpreting the message as PackStream message:" + "\nMessage interpreted : " + messages + "\n" + BytePrinter.hexInOneLine( ByteBuffer.wrap( bytes ), 0, bytes.length ) + /* all bytes */ "\n" + padLeft( offset ) /* the indicator of the error place*/, e ); } } private static String padLeft( int offset ) { StringBuilder output = new StringBuilder(); for ( int i = 0; i < offset; i++ ) { output.append( " " ); if ( (i + 1) % 8 == 0 ) { output.append( " " ); } else { output.append( " " ); } } output.append( "^" ); return output.toString(); } private static byte[] putBytesInOneChunk( byte[] bytes ) { byte[] bytesWithHeadAndTail = new byte[bytes.length + 2 + 2]; // 2 for head and 2 for tail bytesWithHeadAndTail[0] = (byte) (bytes.length >>> 8); bytesWithHeadAndTail[1] = (byte) bytes.length; System.arraycopy( bytes, 0, bytesWithHeadAndTail, 2, bytes.length ); return bytesWithHeadAndTail; } public static List<Message> unpack( List<Message> outcome, MessageFormat.Reader reader ) throws IOException { do { reader.read( new MessageRecordedMessageHandler( outcome ) ); } while ( reader.hasNext() ); return outcome; } /** * This class modified {@link ChunkedInput} to accept a byte array as input and keep track of how many bytes we * have read from the input byte array. */ private static class ByteArrayChunkedInput implements PackInput { private int prePos; private int curPos; private final int size; private final ChunkedInput delegate; public ByteArrayChunkedInput( byte[] bytes ) { prePos = curPos = 0; size = bytes.length; ByteArrayInputStream input = new ByteArrayInputStream( bytes ); ReadableByteChannel ch = Channels.newChannel( input ); this.delegate = new ChunkedInput( ch ) { @Override protected int readChunkSize() throws IOException { prePos = curPos; int chunkSize = super.readChunkSize(); curPos += 2; return chunkSize; } }; } @Override public boolean hasMoreData() throws IOException { return curPos < size; } @Override public byte readByte() { prePos = curPos; byte read = delegate.readByte(); curPos += 1; return read; } @Override public short readShort() { prePos = curPos; short read = delegate.readShort(); curPos += 2; return read; } @Override public int readInt() { prePos = curPos; int read = delegate.readInt(); curPos += 4; return read; } @Override public long readLong() { prePos = curPos; long read = delegate.readLong(); curPos += 8; return read; } @Override public double readDouble() { prePos = curPos; double read = delegate.readDouble(); curPos += 8; return read; } @Override public PackInput readBytes( byte[] into, int offset, int toRead ) { prePos = curPos; PackInput packInput = delegate.readBytes( into, offset, toRead ); curPos += toRead; return packInput; } @Override public byte peekByte() { return delegate.peekByte(); } public Runnable messageBoundaryHook() { // the method will call readChunkSize method so no need to +2 final Runnable runnable = delegate.messageBoundaryHook(); return new Runnable() { @Override public void run() { prePos = curPos; runnable.run(); } }; } } /** * All the interpreted messages will be appended to the input message array even if an error happens when * decoding other messages latter. */ private static class MessageRecordedMessageHandler implements MessageHandler { private final List<Message> outcome; public MessageRecordedMessageHandler( List<Message> outcome ) { this.outcome = outcome; } @Override public void handlePullAllMessage() { outcome.add( new PullAllMessage() ); } @Override public void handleInitMessage( String clientNameAndVersion, Map<String,Value> authToken ) throws IOException { outcome.add( new InitMessage( clientNameAndVersion, authToken ) ); } @Override public void handleRunMessage( String statement, Map<String,Value> parameters ) { outcome.add( new RunMessage( statement, parameters ) ); } @Override public void handleDiscardAllMessage() { outcome.add( new DiscardAllMessage() ); } @Override public void handleResetMessage() { outcome.add( new ResetMessage() ); } @Override public void handleAckFailureMessage() { outcome.add( new AckFailureMessage() ); } @Override public void handleSuccessMessage( Map<String,Value> meta ) { outcome.add( new SuccessMessage( meta ) ); } @Override public void handleRecordMessage( Value[] fields ) { outcome.add( new RecordMessage( fields ) ); } @Override public void handleFailureMessage( String code, String message ) { outcome.add( new FailureMessage( code, message ) ); } @Override public void handleIgnoredMessage() { outcome.add( new IgnoredMessage() ); } } }