/*****************************************************************************
* Copyright (C) 2008 EnterpriseDB Corporation.
* Copyright (C) 2011 Stado Global Development Group.
*
* This file is part of Stado.
*
* Stado 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.
*
* Stado 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 Stado. If not, see <http://www.gnu.org/licenses/>.
*
* You can find Stado at http://www.stado.us
*
****************************************************************************/
/**
*
*/
package org.postgresql.stado.protocol;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import org.postgresql.stado.common.util.Property;
/**
* encapsulates message of Postgres Protocol v. 3.0
*/
public class PgProtocolMessage {
/**
* Postgress uses null-terminated C-style string, so size of a string is
* initially unknown. This value is initial buffer size to read it in
*/
private static final int INITIAL_CAPACITY = 256;
private static final String CHARSET_NAME = Property.get("xdb.charset",
"ISO-8859-1");
// Message types to connect/disconnect
public static final byte MESSAGE_TYPE_INITIAL = 0;
public static final byte MESSAGE_TYPE_AUTHENTICATION = 'R';
public static final byte MESSAGE_TYPE_ERROR_RESPONSE = 'E';
public static final byte MESSAGE_TYPE_BACKEND_KEY_DATA = 'K';
public static final byte MESSAGE_TYPE_PARAMETER_STATUS = 'S';
public static final byte MESSAGE_TYPE_READY_FOR_QUERY = 'Z';
public static final byte MESSAGE_TYPE_PASSWORD_MESSAGE = 'p';
public static final byte MESSAGE_TYPE_TERMINATE = 'X';
public static final byte MESSAGE_TYPE_SSL_YES = 'S';
public static final byte MESSAGE_TYPE_SSL_NO = 'N';
// Message types to execute a query
public static final byte MESSAGE_TYPE_QUERY = 'Q';
public static final byte MESSAGE_TYPE_PARSE = 'P';
public static final byte MESSAGE_TYPE_PARSE_COMPLETE = '1';
public static final byte MESSAGE_TYPE_EMPTY_QUERY_RESPONSE = 'I';
public static final byte MESSAGE_TYPE_BIND = 'B';
public static final byte MESSAGE_TYPE_BIND_COMPLETE = '2';
public static final byte MESSAGE_TYPE_EXECUTE = 'E';
public static final byte MESSAGE_TYPE_COMMAND_COMPLETE = 'C';
public static final byte MESSAGE_TYPE_DESCRIBE = 'D';
public static final byte MESSAGE_TYPE_PARAMETER_DESCRIPTION = 't';
public static final byte MESSAGE_TYPE_NO_DATA = 'n';
public static final byte MESSAGE_TYPE_ROW_DESCRIPTION = 'T';
public static final byte MESSAGE_TYPE_SYNC = 'S';
public static final byte MESSAGE_TYPE_DATA_ROW = 'D';
public static final byte MESSAGE_TYPE_PORTAL_SUSPENDED = 's';
public static final byte MESSAGE_TYPE_CLOSE = 'C';
public static final byte MESSAGE_TYPE_CLOSE_COMPLETE = '3';
// Function calls
public static final byte MESSAGE_TYPE_FUNCTION_CALL = 'F';
public static final byte MESSAGE_TYPE_FUNCTION_CALL_RESPONSE = 'V';
// COPY
public static final byte MESSAGE_TYPE_COPY_IN_RESPONSE = 'G';
public static final byte MESSAGE_TYPE_COPY_OUT_RESPONSE = 'H';
public static final byte MESSAGE_TYPE_COPY_DATA = 'd';
public static final byte MESSAGE_TYPE_COPY_DONE = 'c';
public static final byte MESSAGE_TYPE_COPY_FAIL = 'f';
// Some immutable message classes
public static final PgProtocolMessage MSG_AUTHENTICATION_OK = new PgProtocolMessage(
MESSAGE_TYPE_AUTHENTICATION, new byte[] { 0, 0, 0, 0 });
public static final PgProtocolMessage MSG_AUTHENTICATION_KERBEROS_V4 = new PgProtocolMessage(
MESSAGE_TYPE_AUTHENTICATION, new byte[] { 0, 0, 0, 1 });
public static final PgProtocolMessage MSG_AUTHENTICATION_KERBEROS_V5 = new PgProtocolMessage(
MESSAGE_TYPE_AUTHENTICATION, new byte[] { 0, 0, 0, 2 });
public static final PgProtocolMessage MSG_AUTHENTICATION_CLEARTEXT_PASSWORD = new PgProtocolMessage(
MESSAGE_TYPE_AUTHENTICATION, new byte[] { 0, 0, 0, 3 });
public static final PgProtocolMessage MSG_SSL_YES = new PgProtocolMessage(
MESSAGE_TYPE_SSL_YES, null);
public static final PgProtocolMessage MSG_SSL_NO = new PgProtocolMessage(
MESSAGE_TYPE_SSL_NO, null);
public static final PgProtocolMessage MSG_PARSE_COMPLETE = new PgProtocolMessage(
MESSAGE_TYPE_PARSE_COMPLETE, new byte[0]);
public static final PgProtocolMessage MSG_EMPTY_QUERY_RESPONSE = new PgProtocolMessage(
MESSAGE_TYPE_EMPTY_QUERY_RESPONSE, new byte[0]);
public static final PgProtocolMessage MSG_BIND_COMPLETE = new PgProtocolMessage(
MESSAGE_TYPE_BIND_COMPLETE, new byte[0]);
public static final PgProtocolMessage MSG_NO_DATA = new PgProtocolMessage(
MESSAGE_TYPE_NO_DATA, new byte[0]);
public static final PgProtocolMessage MSG_PORTAL_SUSPENDED = new PgProtocolMessage(
MESSAGE_TYPE_PORTAL_SUSPENDED, new byte[0]);
public static final PgProtocolMessage MSG_CLOSE_COMPLETE = new PgProtocolMessage(
MESSAGE_TYPE_CLOSE_COMPLETE, new byte[0]);
public static final PgProtocolMessage MSG_COPY_DONE = new PgProtocolMessage(
MESSAGE_TYPE_COPY_DONE, new byte[0]);
private byte messageType;
private int messageLength;
private byte[] messageBody;
private int currentPos = 0;
public static final byte[] encodeString(String value) {
try {
return value == null ? null : value.getBytes(CHARSET_NAME);
} catch (UnsupportedEncodingException e) {
// TODO: handle exception
return null;
}
}
public static final String decodeString(byte[] value, int offset, int length) {
try {
return value == null ? null : new String(value, offset, length,
CHARSET_NAME);
} catch (UnsupportedEncodingException e) {
// TODO: handle exception
return null;
}
}
public static final String decodeString(byte[] value) {
try {
return value == null ? null : new String(value, CHARSET_NAME);
} catch (UnsupportedEncodingException e) {
// TODO: handle exception
return null;
}
}
/**
* Construct new protocol message
*
* @param messageType
* @param messageBody
*/
public PgProtocolMessage(byte messageType, byte[] messageBody) {
this.messageType = messageType;
this.messageBody = messageBody;
messageLength = messageBody == null ? messageLength = -1
: messageBody.length;
}
/**
* Length of message data part
*
* @return
*/
public int getLength() {
return messageLength;
}
/**
* Size of message data buffer
*
* @return
*/
public int getCapacity() {
return messageBody == null ? 0 : messageBody.length;
}
/**
* Set size of message data buffer equal or greater then specified
*
* @param capacity
*/
public void ensureCapacity(int capacity) {
if (messageBody == null) {
messageBody = new byte[capacity > INITIAL_CAPACITY ? capacity
: INITIAL_CAPACITY];
messageLength = 0;
} else if (messageBody.length < capacity) {
byte[] newMsgBody = new byte[capacity > messageBody.length * 2 ? capacity
: messageBody.length * 2];
System.arraycopy(messageBody, 0, newMsgBody, 0, messageLength);
messageBody = newMsgBody;
}
}
/**
* Write the message provided ByteBuffer or to the new ByteBuffer if bbuf is
* null to send it over a channel
*
* @param bbuf
* @return
*/
public ByteBuffer getAsByteBuffer(ByteBuffer bbuf) {
if (bbuf == null) {
if (messageLength < 0) {
bbuf = ByteBuffer.allocate(1);
} else {
bbuf = ByteBuffer.allocate(5 + messageLength);
}
}
bbuf.put(messageType);
if (messageLength >= 0) {
bbuf.putInt(messageLength + 4);
if (messageBody != null && messageBody.length > 0) {
bbuf.put(messageBody, 0, messageLength);
}
}
bbuf.flip();
return bbuf;
}
/**
*
* @return
*/
public byte getMessageType() {
return messageType;
}
/**
* Current read position in the data buffer
*
* @return
*/
public int getPosition() {
return currentPos;
}
/**
* Move current read position in the data buffer
*
* @param newPos
*/
public void setPosition(int newPos) {
currentPos = newPos > messageLength ? messageLength : newPos;
}
/**
* Get next int8 (byte) value from the data buffer
*
* @return
*/
public int getInt8() {
return messageBody[currentPos++] & 0xff;
}
/**
* Write value as int8 (byte) into the data buffer
*
* @param value
*/
public void putInt8(int value) {
ensureCapacity(messageLength + 1);
messageBody[currentPos++] = (byte) value;
if (messageLength < currentPos) {
messageLength = currentPos;
}
}
/**
* Get next int16 (short) value from the data buffer
*
* @return
*/
public int getInt16() {
return getInt8() << 8 | getInt8();
}
/**
* Write value as int16 (short) into the data buffer
*
* @param value
*/
public void putInt16(int value) {
ensureCapacity(messageLength + 2);
messageBody[currentPos++] = (byte) (value >> 8);
messageBody[currentPos++] = (byte) value;
if (messageLength < currentPos) {
messageLength = currentPos;
}
}
/**
* Get next int32 (int) value from the data buffer
*
* @return
*/
public int getInt32() {
return getInt8() << 24 | getInt8() << 16 | getInt8() << 8 | getInt8();
}
/**
* Write value as int32 (int) into the data buffer
*
* @param value
*/
public void putInt32(int value) {
ensureCapacity(messageLength + 4);
messageBody[currentPos++] = (byte) (value >> 24);
messageBody[currentPos++] = (byte) (value >> 16);
messageBody[currentPos++] = (byte) (value >> 8);
messageBody[currentPos++] = (byte) value;
if (messageLength < currentPos) {
messageLength = currentPos;
}
}
/**
* Get specified number of int8 (byte) values from the data buffer into an
* array
*
* @return
*/
public int[] getInt8Array(int length) {
if (length < 0) {
return null;
}
int[] result = new int[length];
for (int i = 0; i < length; i++) {
result[i] = getInt8();
}
return result;
}
/**
* Get specified number of int16 (short) values from the data buffer into an
* array
*
* @return
*/
public int[] getInt16Array(int length) {
if (length < 0) {
return null;
}
int[] result = new int[length];
for (int i = 0; i < length; i++) {
result[i] = getInt16();
}
return result;
}
/**
* Get specified number of int32 (int) values from the data buffer into an
* array
*
* @return
*/
public int[] getInt32Array(int length) {
if (length < 0) {
return null;
}
int[] result = new int[length];
for (int i = 0; i < length; i++) {
result[i] = getInt32();
}
return result;
}
/**
* Get specified number of bytes from the data buffer into a byte array
*
* @return
*/
public byte[] getBytes(int length) {
if (length < 0) {
return null;
}
byte[] result = new byte[length];
if (length > 0) {
System.arraycopy(messageBody, currentPos, result, 0, length);
currentPos += length;
}
return result;
}
/**
* Write contents of supplied byte array into the data buffer
*
* @param value
*/
public void putBytes(byte[] value) {
putBytes(value, 0, value.length);
}
/**
* Write contents of supplied byte array into the data buffer
*
* @param value
* @param offset
* @param length
*/
public void putBytes(byte[] value, int offset, int length) {
if (value == null || value.length == 0) {
return;
}
ensureCapacity(messageLength + length);
System.arraycopy(value, offset, messageBody, currentPos, length);
currentPos += length;
if (messageLength < currentPos) {
messageLength = currentPos;
}
}
/**
* Get next null-terminated String from the data buffer
*
* @return
*/
public String getString() {
byte[] bbuf = new byte[INITIAL_CAPACITY];
byte value = (byte) getInt8();
int idx = 0;
for (; value != 0; idx++) {
if (idx == bbuf.length) {
byte[] newbuf = new byte[bbuf.length * 2];
System.arraycopy(bbuf, 0, newbuf, 0, idx);
bbuf = newbuf;
}
bbuf[idx] = value;
value = (byte) getInt8();
}
return decodeString(bbuf, 0, idx);
}
/**
* Write contents of supplied String into the data buffer
*
* @param value
*/
public void putString(String value) {
if (value != null) {
putBytes(encodeString(value));
}
putInt8(0);
}
/**
* Returns true if data buffer has unread bytes in the data buffer
*
* @return
*/
public boolean hasMoreData() {
return messageLength > currentPos;
}
}