All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.neo4j.bolt.v1.packstream.PackStream Maven / Gradle / Ivy

/*
 * Copyright (c) 2002-2017 "Neo Technology,"
 * Network Engine for Objects in Lund AB [http://neotechnology.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package org.neo4j.bolt.v1.packstream;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;

import org.neo4j.bolt.v1.packstream.utf8.UTF8Encoder;

/**
 * PackStream is a messaging serialisation format heavily inspired by MessagePack.
 * The key differences are in the type system itself which (among other things) replaces extensions with structures.
 * The Packer and Unpacker implementations are also faster than their MessagePack counterparts.
 * 

* Note that several marker byte values are RESERVED for future use. * Extra markers should not be added casually and such additions must be follow a strict process involving both * client and server software. *

* The table below shows all allocated marker byte values. *

*

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
MarkerBinaryTypeDescription
00..7F0xxxxxxx+TINY_INTInteger 0 to 127
80..8F1000xxxxTINY_STRING
90..9F1001xxxxTINY_LIST
A0..AF1010xxxxTINY_MAP
B0..BF1011xxxxTINY_STRUCT
C011000000NULL
C111000001FLOAT_6464-bit floating point number * (double)
C211000010FALSEBoolean false
C311000011TRUEBoolean true
C4..C7110001xxRESERVED
C811001000INT_88-bit signed integer
C911001001INT_816-bit signed integer
CA11001010INT_832-bit signed integer
CB11001011INT_864-bit signed integer
CC11001100BYTES_8Byte string (fewer than 28 * bytes)
CD11001101BYTES_16Byte string (fewer than 216 * bytes)
CE11001110BYTES_32Byte string (fewer than 232 * bytes)
CF11001111RESERVED
D011010000STRING_8UTF-8 encoded string (fewer than * 28 bytes)
D111010001STRING_16UTF-8 encoded string (fewer than * 216 bytes)
D211010010STRING_32UTF-8 encoded string (fewer than * 232 bytes)
D311010011RESERVED
D411010100LIST_8List (fewer than 28 * items)
D511010101LIST_16List (fewer than 216 * items)
D611010110LIST_32List (fewer than 232 * items)
D711010111RESERVED
D811011000MAP_8Map (fewer than 28 key:value * pairs)
D911011001MAP_16Map (fewer than 216 key:value * pairs)
DA11011010MAP_32Map (fewer than 232 key:value * pairs)
DB11011011RESERVED
DC11011100STRUCT_8Structure (fewer than 28 * fields)
DD11011101STRUCT_16Structure (fewer than 216 * fields)
DE11011110STRUCT_32Structure (fewer than 232 * fields)
DF11011111RESERVED
E0..EF1110xxxxRESERVED
F0..FF1111xxxx-TINY_INTInteger -1 to -16
*/ public class PackStream { public static final byte TINY_STRING = (byte) 0x80; public static final byte TINY_LIST = (byte) 0x90; public static final byte TINY_MAP = (byte) 0xA0; public static final byte TINY_STRUCT = (byte) 0xB0; public static final byte NULL = (byte) 0xC0; public static final byte FLOAT_64 = (byte) 0xC1; public static final byte FALSE = (byte) 0xC2; public static final byte TRUE = (byte) 0xC3; public static final byte RESERVED_C4 = (byte) 0xC4; public static final byte RESERVED_C5 = (byte) 0xC5; public static final byte RESERVED_C6 = (byte) 0xC6; public static final byte RESERVED_C7 = (byte) 0xC7; public static final byte INT_8 = (byte) 0xC8; public static final byte INT_16 = (byte) 0xC9; public static final byte INT_32 = (byte) 0xCA; public static final byte INT_64 = (byte) 0xCB; public static final byte BYTES_8 = (byte) 0xCC; public static final byte BYTES_16 = (byte) 0xCD; public static final byte BYTES_32 = (byte) 0xCE; public static final byte RESERVED_CF = (byte) 0xCF; public static final byte STRING_8 = (byte) 0xD0; public static final byte STRING_16 = (byte) 0xD1; public static final byte STRING_32 = (byte) 0xD2; public static final byte RESERVED_D3 = (byte) 0xD3; public static final byte LIST_8 = (byte) 0xD4; public static final byte LIST_16 = (byte) 0xD5; public static final byte LIST_32 = (byte) 0xD6; public static final byte LIST_STREAM = (byte) 0xD7; public static final byte MAP_8 = (byte) 0xD8; public static final byte MAP_16 = (byte) 0xD9; public static final byte MAP_32 = (byte) 0xDA; public static final byte MAP_STREAM = (byte) 0xDB; public static final byte STRUCT_8 = (byte) 0xDC; public static final byte STRUCT_16 = (byte) 0xDD; public static final byte RESERVED_DE = (byte) 0xDE; public static final byte END_OF_STREAM = (byte) 0xDF; public static final byte RESERVED_E0 = (byte) 0xE0; public static final byte RESERVED_E1 = (byte) 0xE1; public static final byte RESERVED_E2 = (byte) 0xE2; public static final byte RESERVED_E3 = (byte) 0xE3; public static final byte RESERVED_E4 = (byte) 0xE4; public static final byte RESERVED_E5 = (byte) 0xE5; public static final byte RESERVED_E6 = (byte) 0xE6; public static final byte RESERVED_E7 = (byte) 0xE7; public static final byte RESERVED_E8 = (byte) 0xE8; public static final byte RESERVED_E9 = (byte) 0xE9; public static final byte RESERVED_EA = (byte) 0xEA; public static final byte RESERVED_EB = (byte) 0xEB; public static final byte RESERVED_EC = (byte) 0xEC; public static final byte RESERVED_ED = (byte) 0xED; public static final byte RESERVED_EE = (byte) 0xEE; public static final byte RESERVED_EF = (byte) 0xEF; public static final long UNKNOWN_SIZE = -1; private static final long PLUS_2_TO_THE_31 = 2147483648L; private static final long PLUS_2_TO_THE_15 = 32768L; private static final long PLUS_2_TO_THE_7 = 128L; private static final long MINUS_2_TO_THE_4 = -16L; private static final long MINUS_2_TO_THE_7 = -128L; private static final long MINUS_2_TO_THE_15 = -32768L; private static final long MINUS_2_TO_THE_31 = -2147483648L; private PackStream() { } private static PackType type( byte markerByte ) { final byte markerHighNibble = (byte) (markerByte & 0xF0); switch ( markerHighNibble ) { case TINY_STRING: return PackType.STRING; case TINY_LIST: return PackType.LIST; case TINY_MAP: return PackType.MAP; case TINY_STRUCT: return PackType.STRUCT; default: break; } if ( markerByte >= MINUS_2_TO_THE_4 ) { return PackType.INTEGER; } switch ( markerByte ) { case NULL: return PackType.NULL; case TRUE: case FALSE: return PackType.BOOLEAN; case FLOAT_64: return PackType.FLOAT; case BYTES_8: case BYTES_16: case BYTES_32: return PackType.BYTES; case STRING_8: case STRING_16: case STRING_32: return PackType.STRING; case LIST_8: case LIST_16: case LIST_32: case LIST_STREAM: return PackType.LIST; case MAP_8: case MAP_16: case MAP_32: case MAP_STREAM: return PackType.MAP; case STRUCT_8: case STRUCT_16: return PackType.STRUCT; case END_OF_STREAM: return PackType.END_OF_STREAM; case INT_8: case INT_16: case INT_32: case INT_64: return PackType.INTEGER; default: return PackType.RESERVED; } } public static class Packer { private static final char PACKED_CHAR_START_CHAR = (char) 32; private static final char PACKED_CHAR_END_CHAR = (char) 126; private static final String[] PACKED_CHARS = prePackChars(); private PackOutput out; private UTF8Encoder utf8 = UTF8Encoder.fastestAvailableEncoder(); public Packer( PackOutput out ) { this.out = out; } private static String[] prePackChars() { int size = PACKED_CHAR_END_CHAR + 1 - PACKED_CHAR_START_CHAR; String[] packedChars = new String[size]; for ( int i = 0; i < size; i++ ) { packedChars[i] = String.valueOf( (char) (i + PACKED_CHAR_START_CHAR) ); } return packedChars; } public void flush() throws IOException { out.flush(); } public void packNull() throws IOException { out.writeByte( NULL ); } public void pack( boolean value ) throws IOException { out.writeByte( value ? TRUE : FALSE ); } public void pack( long value ) throws IOException { if ( value >= MINUS_2_TO_THE_4 && value < PLUS_2_TO_THE_7 ) { out.writeByte( (byte) value ); } else if ( value >= MINUS_2_TO_THE_7 && value < MINUS_2_TO_THE_4 ) { out.writeByte( INT_8 ).writeByte( (byte) value ); } else if ( value >= MINUS_2_TO_THE_15 && value < PLUS_2_TO_THE_15 ) { out.writeByte( INT_16 ).writeShort( (short) value ); } else if ( value >= MINUS_2_TO_THE_31 && value < PLUS_2_TO_THE_31 ) { out.writeByte( INT_32 ).writeInt( (int) value ); } else { out.writeByte( INT_64 ).writeLong( value ); } } public void pack( double value ) throws IOException { out.writeByte( FLOAT_64 ).writeDouble( value ); } public void pack( char character ) throws IOException { if ( character >= PACKED_CHAR_START_CHAR && character <= PACKED_CHAR_END_CHAR ) { pack( PACKED_CHARS[character - PACKED_CHAR_START_CHAR] ); } else { pack( String.valueOf( character ) ); } } public void pack( byte[] value ) throws IOException { if ( value == null ) { packNull(); } else { packBytesHeader( value.length ); out.writeBytes( value, 0, value.length ); } } public void pack( String value ) throws IOException { if ( value == null ) { packNull(); } else { ByteBuffer encoded = utf8.encode( value ); packStringHeader( encoded.remaining() ); out.writeBytes( encoded ); } } private void packBytesHeader( int size ) throws IOException { if ( size <= Byte.MAX_VALUE ) { out.writeShort( (short) (BYTES_8 << 8 | size) ); } else if ( size <= Short.MAX_VALUE ) { out.writeByte( BYTES_16 ).writeShort( (short) size ); } else { out.writeByte( BYTES_32 ).writeInt( size ); } } private void packStringHeader( int size ) throws IOException { if ( size < 0x10 ) { out.writeByte( (byte) (TINY_STRING | size) ); } else if ( size <= Byte.MAX_VALUE ) { out.writeShort( (short) (STRING_8 << 8 | size) ); } else if ( size <= Short.MAX_VALUE ) { out.writeByte( STRING_16 ).writeShort( (short) size ); } else { out.writeByte( STRING_32 ).writeInt( size ); } } public void packListHeader( int size ) throws IOException { if ( size < 0x10 ) { out.writeByte( (byte) (TINY_LIST | size) ); } else if ( size <= Byte.MAX_VALUE ) { out.writeShort( (short) (LIST_8 << 8 | size) ); } else if ( size <= Short.MAX_VALUE ) { out.writeByte( LIST_16 ).writeShort( (short) size ); } else { out.writeByte( LIST_32 ).writeInt( size ); } } public void packListStreamHeader() throws IOException { out.writeByte( LIST_STREAM ); } public void packMapHeader( int size ) throws IOException { if ( size < 0x10 ) { out.writeByte( (byte) (TINY_MAP | size) ); } else if ( size <= Byte.MAX_VALUE ) { out.writeShort( (short) (MAP_8 << 8 | size) ); } else if ( size <= Short.MAX_VALUE ) { out.writeByte( MAP_16 ).writeShort( (short) size ); } else { out.writeByte( MAP_32 ).writeInt( size ); } } public void packMapStreamHeader() throws IOException { out.writeByte( MAP_STREAM ); } public void packStructHeader( int size, byte signature ) throws IOException { if ( size < 0x10 ) { out.writeShort( (short) ((byte) (TINY_STRUCT | size) << 8 | (signature & 0xFF)) ); } else if ( size <= Byte.MAX_VALUE ) { out.writeByte( STRUCT_8 ).writeByte( (byte) size ).writeByte( signature ); } else if ( size <= Short.MAX_VALUE ) { out.writeByte( STRUCT_16 ).writeShort( (short) size ).writeByte( signature ); } else { throw new Overflow( "Structures cannot have more than " + Short.MAX_VALUE + " fields" ); } } public void packEndOfStream() throws IOException { out.writeByte( END_OF_STREAM ); } } public static class Unpacker { private static final byte[] EMPTY_BYTE_ARRAY = {}; private PackInput in; public Unpacker( PackInput in ) { this.in = in; } public boolean hasNext() throws IOException { return in.hasMoreData(); } // TODO: This currently returns the number of fields in the struct. In 99% of cases we will look at the struct // signature to determine how to read it, suggest we make that what we return here, // and have the number of fields available through some alternate optional mechanism. public long unpackStructHeader() throws IOException { final byte markerByte = in.readByte(); final byte markerHighNibble = (byte) (markerByte & 0xF0); final byte markerLowNibble = (byte) (markerByte & 0x0F); if ( markerHighNibble == TINY_STRUCT ) { return markerLowNibble; } switch ( markerByte ) { case STRUCT_8: return unpackUINT8(); case STRUCT_16: return unpackUINT16(); default: throw new Unexpected( PackType.STRUCT, markerByte ); } } public char unpackStructSignature() throws IOException { return (char) in.readByte(); } public long unpackListHeader() throws IOException { final byte markerByte = in.readByte(); final byte markerHighNibble = (byte) (markerByte & 0xF0); final byte markerLowNibble = (byte) (markerByte & 0x0F); if ( markerHighNibble == TINY_LIST ) { return markerLowNibble; } switch ( markerByte ) { case LIST_8: return unpackUINT8(); case LIST_16: return unpackUINT16(); case LIST_32: return unpackUINT32(); case LIST_STREAM: return UNKNOWN_SIZE; default: throw new Unexpected( PackType.LIST, markerByte ); } } public long unpackMapHeader() throws IOException { final byte markerByte = in.readByte(); final byte markerHighNibble = (byte) (markerByte & 0xF0); final byte markerLowNibble = (byte) (markerByte & 0x0F); if ( markerHighNibble == TINY_MAP ) { return markerLowNibble; } switch ( markerByte ) { case MAP_8: return unpackUINT8(); case MAP_16: return unpackUINT16(); case MAP_32: return unpackUINT32(); case MAP_STREAM: return UNKNOWN_SIZE; default: throw new Unexpected( PackType.MAP, markerByte ); } } public int unpackInteger() throws IOException { final byte markerByte = in.readByte(); if ( markerByte >= MINUS_2_TO_THE_4 ) { return markerByte; } switch ( markerByte ) { case INT_8: return in.readByte(); case INT_16: return in.readShort(); case INT_32: return in.readInt(); case INT_64: throw new Overflow( "Unexpectedly large Integer value unpacked (" + in.readLong() + ")" ); default: throw new Unexpected( PackType.INTEGER, markerByte ); } } public long unpackLong() throws IOException { final byte markerByte = in.readByte(); if ( markerByte >= MINUS_2_TO_THE_4 ) { return markerByte; } switch ( markerByte ) { case INT_8: return in.readByte(); case INT_16: return in.readShort(); case INT_32: return in.readInt(); case INT_64: return in.readLong(); default: throw new Unexpected( PackType.INTEGER, markerByte ); } } public double unpackDouble() throws IOException { final byte markerByte = in.readByte(); if ( markerByte == FLOAT_64 ) { return in.readDouble(); } throw new Unexpected( PackType.FLOAT, markerByte ); } public byte[] unpackBytes() throws IOException { int size = unpackBytesHeader(); return unpackRawBytes( size ); } public String unpackString() throws IOException { return new String( unpackUTF8(), StandardCharsets.UTF_8 ); } public int unpackBytesHeader() throws IOException { final byte markerByte = in.readByte(); int size; switch ( markerByte ) { case BYTES_8: size = unpackUINT8(); break; case BYTES_16: size = unpackUINT16(); break; case BYTES_32: { long longSize = unpackUINT32(); if ( longSize <= Integer.MAX_VALUE ) { size = (int) longSize; } else { throw new Overflow( "BYTES_32 too long for Java" ); } break; } default: throw new Unexpected( PackType.BYTES, markerByte ); } return size; } public int unpackStringHeader() throws IOException { final byte markerByte = in.readByte(); final byte markerHighNibble = (byte) (markerByte & 0xF0); final byte markerLowNibble = (byte) (markerByte & 0x0F); int size; if ( markerHighNibble == TINY_STRING ) { size = markerLowNibble; } else { switch ( markerByte ) { case STRING_8: size = unpackUINT8(); break; case STRING_16: size = unpackUINT16(); break; case STRING_32: { long longSize = unpackUINT32(); if ( longSize <= Integer.MAX_VALUE ) { size = (int) longSize; } else { throw new Overflow( "STRING_32 too long for Java" ); } break; } default: throw new Unexpected( PackType.STRING, markerByte ); } } return size; } public byte[] unpackUTF8() throws IOException { int size = unpackStringHeader(); return unpackRawBytes( size ); } public boolean unpackBoolean() throws IOException { final byte markerByte = in.readByte(); switch ( markerByte ) { case TRUE: return true; case FALSE: return false; default: throw new Unexpected( PackType.BOOLEAN, markerByte ); } } public void unpackNull() throws IOException { final byte markerByte = in.readByte(); assert markerByte == NULL; } private int unpackUINT8() throws IOException { return in.readByte() & 0xFF; } private int unpackUINT16() throws IOException { return in.readShort() & 0xFFFF; } private long unpackUINT32() throws IOException { return in.readInt() & 0xFFFFFFFFL; } public void unpackEndOfStream() throws IOException { final byte markerByte = in.readByte(); assert markerByte == END_OF_STREAM; } private byte[] unpackRawBytes( int size ) throws IOException { if ( size == 0 ) { return EMPTY_BYTE_ARRAY; } else { byte[] heapBuffer = new byte[size]; unpackRawBytesInto( heapBuffer, 0, heapBuffer.length ); return heapBuffer; } } private void unpackRawBytesInto( byte[] buffer, int offset, int size ) throws IOException { if ( size > 0 ) { in.readBytes( buffer, offset, size ); } } public PackType peekNextType() throws IOException { final byte markerByte = in.peekByte(); return type( markerByte ); } } public static class PackStreamException extends IOException { public PackStreamException( String message ) { super( message ); } } public static class EndOfStream extends PackStreamException { public EndOfStream( String message ) { super( message ); } } public static class Overflow extends PackStreamException { public Overflow( String message ) { super( message ); } } public static class Unexpected extends PackStreamException { public Unexpected( PackType expectedType, byte unexpectedMarkerByte ) { super( "Wrong type received. Expected " + expectedType + ", received: " + type( unexpectedMarkerByte ) + " " + "(" + toHexString( unexpectedMarkerByte ) + ")." ); } private static String toHexString( byte unexpectedMarkerByte ) { String s = Integer.toHexString( unexpectedMarkerByte ); if ( s.length() > 2 ) { s = s.substring( 0, 2 ); } else if ( s.length() < 2 ) { return "0" + s; } return "0x" + s; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy