/*
* Citrusleaf Aerospike Java Library
*
* Copyright 2009-2010 by Citrusleaf, Inc. All rights reserved.
*
* Availability of this source code to partners and customers includes
* redistribution rights covered by individual contract. Please check
* your contract for exact rights and responsibilities.
*/
package net.citrusleaf;
import gnu.crypto.hash.*;
// import org.bouncycastle.crypto.digests.GeneralDigest;
// import org.bouncycastle.crypto.digests.RIPEMD160Digest;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.*;
import net.citrusleaf.CitrusleafClient.*;
public class CLBuffer {
// NB - it's possible, and cooler, to use enums for things like this,
// but they're really the constant numbers used in the wire protocol.
// Java enums are annoying to map to numbers.
// This is easier and more readable.
public final static int FIELD_TYPE_NAMESPACE = 0;
public final static int FIELD_TYPE_TABLE = 1;
public final static int FIELD_TYPE_KEY = 2;
public final static int FIELD_TYPE_BIN = 3;
public final static int FIELD_TYPE_DIGEST_RIPE = 4;
public final static int FIELD_TYPE_GU_TID = 5;
public final static int FIELD_TYPE_DIGEST_RIPE_ARRAY = 6;
public final static int DIGEST_SIZE = 20;
// Don't want to keep pulling these objects over and over ---
@SuppressWarnings("unchecked")
private static Class g_stringClass = null;
@SuppressWarnings("unchecked")
private static Class g_intClass = null;
@SuppressWarnings("unchecked")
private static Class g_longClass = null;
@SuppressWarnings("unchecked")
private static Class g_byteArrayClass = null;
private final static int OP_READ = 1;
private final static int OP_WRITE = 2;
@SuppressWarnings("unused") private final static int OP_WRITE_UNIQUE = 3;
@SuppressWarnings("unused") private final static int OP_WRITE_NOW = 4;
@SuppressWarnings("unused") private final static int OP_ADD = 5;
private final static int OP_APPEND = 6;
// byte - 1 byte
// size - 7 bytes
// op - 1 byte
// particle type - 1 byte
// version - 1 byte
// name_sz - 1 byte
// name N
// per-particle data
//
// Adds the output bytes to the collection
// return value is the new offset
// Throws an array out of bounds if buffer is too small
private int
makeOp(int op, String name, Object o, byte[] buf, int offset, byte[] java_serialized_bytes) throws SerializeException
{
int nameLen = stringToUtf8(name, buf, offset + 8);
// only a true write needs the write data from object
// the number 4 is the size *after* the size field
int field_sz = 4 + nameLen;
ParticleBytesReturn pbr = null;
if (op == OP_WRITE || op == OP_ADD || op == OP_APPEND) {
pbr = objectToParticleBytes(o,buf, 8 + nameLen + offset, java_serialized_bytes);
field_sz += pbr.size;
}
set_htonl(field_sz, buf, offset);
buf[offset+4] = (byte) op;
buf[offset+5] = (byte) (pbr == null ? PARTICLE_TYPE_NULL : pbr.type);
buf[offset+6] = (byte) 0; // version
buf[offset+7] = (byte) nameLen;
return(field_sz + 4);
}
private int makeOpEstimate(Object o) {
int estimate = 8 + 32;
if (o == null) {
return(estimate);
}
Class c = o.getClass();
if (g_byteArrayClass.isAssignableFrom( c ) == true) {
byte[] b = (byte[]) o;
estimate += b.length;
}
else if (g_stringClass.isAssignableFrom( c )==true ) {
String s = (String) o;
estimate += s.length();
} else if (g_intClass.isAssignableFrom( c )==true) {
estimate += 9;
}
else if (g_longClass.isAssignableFrom( c )==true) {
estimate += 9;
}
else {
// regrettably, there's not an easy way to get an estimate out of the JBLOB system. return 0 so caller
// can do the serialization once to find the length
estimate = 0;
}
return(estimate);
}
//
// Sets the 5 bytes of the field header
//
static private int set_field_header(int type, long sz, byte[] buf, int offset)
{
// CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"set_field_header: sz "+sz+" offset "+offset);
set_htonl((int)(sz+1),buf, offset);
offset+=4;
buf[offset] = (byte) type;
offset++;
// CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"bytes: "+buf[offset-1]+" "+buf[offset]+" "+buf[offset+1]+" "+buf[offset+2]);
return(5);
}
static public void set_htonll(long v, byte[] buf, int offset)
{
for (int i=7;i>=0;i--) {
buf[offset+i] = (byte) (v & 0xff);
v >>>= 8;
}
}
static public void set_htonl(int v, byte[] buf, int offset)
{
for (int i=3;i>=0;i--) {
buf[offset+i] = (byte) (v & 0xff);
v >>>= 8;
}
}
static public void set_htons(int v, byte[] buf, int offset)
{
buf[offset] = (byte) ((v >> 8) & 0xff);
buf[offset+1] = (byte) (v & 0xff);
}
static private int get_unsigned(byte b) {
int r = b;
if (r < 0) {
r = r & 0x7f;
r = r | 0x80;
}
return(r);
}
static public long get_ntohll(byte[] buf, int offset) {
long a = 0;
for (int i=0;i<8;i++) {
a <<= 8;
a |= get_unsigned(buf[offset+i]);
}
return(a);
}
static public int get_ntohl(byte[] buf, int offset) {
return (
((buf[offset] & 0xFF) << 24) |
((buf[offset+1] & 0xFF) << 16) |
((buf[offset+2] & 0xFF) << 8) |
(buf[offset+3] & 0xFF)
);
}
static public int get_ntohl_intel(byte[] buf, int offset) {
return (
((buf[offset+3] & 0xFF) << 24) |
((buf[offset+2] & 0xFF) << 16) |
((buf[offset+1] & 0xFF) << 8) |
(buf[offset] & 0xFF)
);
}
static public int get_ntohs(byte[] buf, int offset) {
return ( ((buf[offset] & 0xFF) << 8) + (buf[offset+1] & 0xFF) );
}
public byte[] getDigest(String set, Object key) {
int set_len, key_len, set_offset, key_offset;
byte[] digest;
IMessageDigest md = HashFactory.getInstance("RIPEMD-160");
byte[] buf = new byte[256]; //for holding set and key
set_offset = 0;
set_len = stringToUtf8(set, buf, set_offset);
key_offset = set_len+1;
key_len = keyToBytes(key, buf, key_offset);
md.update(buf, set_offset, set_len);
md.update(buf, key_offset, key_len);
digest = md.digest();
return digest;
}
public static String byteArrayToHexString(byte[] b) {
StringBuffer sb = new StringBuffer(b.length * 2);
for (int i = 0; i < b.length; i++) {
int v = b[i] & 0xff;
if (v < 16) {
sb.append('0');
}
sb.append(Integer.toHexString(v));
}
return sb.toString();
}
public int keyToBytes(Object key, byte[] buf, int key_offset) {
try {
ParticleBytesReturn pbr = objectToParticleBytes(key, buf, key_offset+1, null);
buf[key_offset] = (byte)pbr.type;
return (pbr.size + 1);
} catch (Exception e) {
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"CLBuffer getDigest: exception "+e.toString());
return 0;
}
}
//
// All requests have namespace, table, key, so make a byte array that includes
// all those fields, I think that's the fastest way
// Makes three headers, really
// 1 type = namespace
// 3 len
// [namespace bytes]
// 1 type = table
// 7 len
// [table bytes]
// 1 type = key
// 7 len
// 1 particle type
// key particle bytes
private int
makeFields(byte[] buf, int offset)
{
int initial_offset = offset, set_offset = 0, key_offset = 0, key_len = 0, set_len = 0, digest_offset =0, digest_len = 0;
if (ns != null) {
int ns_len = stringToUtf8(ns, buf, offset+5);
offset += set_field_header(FIELD_TYPE_NAMESPACE, ns_len, buf, offset) + ns_len;
}
// If digest exists, get the offset for the digest header and length
if (digest != null) {
System.arraycopy(digest, 0, buf, offset+5, digest.length);
offset += set_field_header(FIELD_TYPE_DIGEST_RIPE,digest.length, buf, offset);
offset += digest.length;
} else {
if (set != null){
set_offset = offset + 5;
set_len = stringToUtf8(set, buf, offset+5);
offset += set_field_header(FIELD_TYPE_TABLE, set_len, buf, offset) + set_len;
}
if (key != null) {
ParticleBytesReturn pbr;
try {
pbr = objectToParticleBytes(key, buf, offset + 1 + 5, null);
} catch (Exception e) {
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"Asbuffer makeFields prepare: exception "+e.toString());
return(0);
}
offset += set_field_header(FIELD_TYPE_KEY, pbr.size+1, buf, offset);
key_offset = offset;
buf[offset] = (byte)pbr.type;
offset++;
offset += pbr.size;
key_len = pbr.size + 1;
}
}
// THIS VERSION IS GNU.DRYPTO, which is faster than some
if (partition_id == -1) {
if (digest != null) {
partition_id = get_ntohl_intel(digest, 0);
} else {
IMessageDigest md = HashFactory.getInstance("RIPEMD-160");
md.update(buf, set_offset, set_len);
md.update(buf, key_offset, key_len);
byte[] dig = md.digest();
partition_id = get_ntohl_intel(dig, 0);
}
// CAN'T USE MOD directly - mod will give negative numbers. First AND
// makes positive and negative correctly, then mod.
partition_id = (partition_id & 0xFFFF) % CLConnectionManager.n_server_partitions;
}
// BOUNCYCASTLE - seems quite a bit slower than gnu.crypto
// if (partition_id == -1) {
// GeneralDigest md = new RIPEMD160Digest();
// md.update(buf, set_offset, set_len);
// md.update(buf, key_offset, key_len);
// byte[] dig = new byte[20];
// md.doFinal(dig,0);
// partition_id = get_ntohl_intel(dig, 0);
// partition_id = partition_id & 0xFFF;
// }
return(offset-initial_offset);
}
private int makeBatchFields(byte[] buf, int offset, ArrayList<byte[]> alldigests) {
int initial_offset = offset;
//Prepare the namespace field
int ns_len = stringToUtf8(ns, buf, offset+5);
offset += set_field_header(FIELD_TYPE_NAMESPACE, ns_len, buf, offset) + ns_len;
//Prepare the digests field metadata
int digest_len = alldigests.size() * DIGEST_SIZE;
offset += set_field_header(FIELD_TYPE_DIGEST_RIPE_ARRAY, digest_len, buf, offset);
//Write out all the digests as a field
for (Iterator<byte[]> it=alldigests.iterator() ; it.hasNext() ; )
{
byte[] digest = it.next();
for (int i=0 ; i<digest.length ; i++)
{
buf[offset+i] = digest[i];
}
offset += digest.length;
}
return(offset-initial_offset);
}
//
// All requests have namespace, table, key, so make a byte array that includes
// all those fields, I think that's the fastest way
// Makes three headers, really
// 1 type = namespace
// 3 len
// [namespace bytes]
// 1 type = table
// 7 len
// [table bytes]
// 1 type = key
// 7 len
// 1 particle type
// key particle bytes
private int
makeFieldsEstimate() throws SerializeException
{
int offset = 0, op_estimate_sz;
byte[] value = null;
int ns_len = 0;
if (ns != null) {
try {
value = ns.getBytes("UTF8");
ns_len = value.length;
} catch (UnsupportedEncodingException e) {
// If you can't encode a UTF8 string you're in sad, sad shape
throw new SerializeException();
}
offset += 5 + ns_len;
}
if (digest != null) {
offset += 5 + digest.length;
}
int set_len = 0;
if (set != null) {
try {
value = set.getBytes("UTF8");
set_len = value.length;
} catch (UnsupportedEncodingException e) {
// If you can't encode a UTF8 string you're in sad, sad shape
throw new SerializeException();
}
offset += 5 + set_len;
}
try {
if (key != null) {
op_estimate_sz = makeOpEstimate(key);
if (op_estimate_sz == 0) {
byte[] jblob = objectToByteArray(key);
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"Warning: Key is a serialized object\n");
if (jblob == null)
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"jblob is NULL!!!\n");
op_estimate_sz = 8 + 32 + jblob.length;
}
offset += op_estimate_sz;
offset++;
}
} catch (Exception e) {
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"Asbuffer makeFieldsEstimate: exception "+e.toString());
return(0);
}
offset += 5;
return(offset);
}
private final int INFO1_READ = (1 << 0); // contains a read operation
private final int INFO1_GET_ALL = (1 << 1); // get all bins, period
@SuppressWarnings("unused")
private final int INFO1_GET_ALL_NODATA = (1 << 2); // get all bins WITHOUT data (currently unimplemented)
@SuppressWarnings("unused")
private final int INFO1_VERIFY = (1 << 3); // verify is a GET transaction that includes data, and assert if the data aint right
private final int INFO1_XDS = (1 << 4); // Operation is being performed by XDS
private final int INFO1_NOBINDATA = (1 << 5); // Do not read the bins
private final int INFO2_WRITE = (1 << 0); // contains a write semantic
private final int INFO2_DELETE = (1 << 1); // fling a record into the belly of Moloch
private final int INFO2_GENERATION = (1 << 2); // pay attention to the generation
private final int INFO2_GENERATION_GT = (1 << 3); // apply write if new generation >= old, good for restore
private final int INFO2_GENERATION_DUP = (1 << 4); // if a generation collision, create a duplicate
private final int INFO2_WRITE_UNIQUE = (1 << 5); // write only if it doesn't exist
@SuppressWarnings("unused")
private final int INFO2_WRITE_BINUNIQUE = (1 << 6);
@SuppressWarnings("unused")
private final int INFO3_LAST = (1 << 0); // this is the last of a multi-part message
@SuppressWarnings("unused")
private final int INFO3_TRACE = (1 << 1); // apply server trace logging for this transaction
@SuppressWarnings("unused")
private final int INFO3_TOMBSTONE = (1 << 2); // if set on response, a version was a delete tombstone
private final long CL_MSG_VERSION = 2L;
private final long AS_MSG_TYPE = 3L;
private final int CL_MSG_HEADER_LEN = 8;
private final int AS_MSG_HEADER_LEN = 22;
public String ns;
public String set;
public Object key;
public byte [] digest;
public long partition_id;
public CLConnectionManager.PartitionType partition_type;
private ClWriteOptions cl_wp;
private Collection<String> readBins;
private Collection<ClBin> writeBins;
private Collection<ClBin> addBins;
private Collection<ClBin> appendBins;
private boolean readAll;
private boolean readIterate;
private boolean readNoBindata;
private ScanCallback callback;
private Object scanObject;
int generation;
int expiration;
boolean delete;
//parameters for batch operations
private boolean batchread = false;
private boolean gotlastmsg = false;
private ArrayList<Object> allkeys = null;
private ArrayList<byte[]> alldigests = null;
private byte msg_header_buf[]; // A 30 byte buffer used for reading just the response header
public byte msg_write_buf[];
public long msg_write_len;
public byte msg_read_buf[]; // used to write the request, and read in the response
public CLBuffer()
{
if (g_stringClass == null) {
try {
g_stringClass = Class.forName("java.lang.String");
g_intClass = Class.forName("java.lang.Integer");
g_longClass = Class.forName("java.lang.Long");
byte[] b = new byte[1];
g_byteArrayClass = b.getClass();
} catch (Exception e) {
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO," strings and ints don't exist. Blow me.");
return;
}
}
ns = null;
set = null;
key = null;
digest = null;
cl_wp = null;
partition_id = -1;
partition_type = CLConnectionManager.PartitionType.READ; // write is more generic
readBins = null;
writeBins = null;
addBins = null;
appendBins = null;
readAll = false;
readIterate = false;
readNoBindata = false;
generation = 0;
delete = false;
batchread = false;
allkeys = null;
alldigests = null;
msg_header_buf = new byte[CL_MSG_HEADER_LEN + AS_MSG_HEADER_LEN];
msg_write_buf = null;
msg_read_buf = null;
msg_write_len = 0;
}
void reset() {
ns = null;
set = null;
key = null;
digest = null;
cl_wp = null;
partition_id = -1;
partition_type = CLConnectionManager.PartitionType.READ; // write is more generic
readBins = null;
writeBins = null;
addBins = null;
appendBins = null;
readAll = false;
readIterate = false;
readNoBindata = false;
generation = 0;
delete = false;
batchread = false;
allkeys = null;
alldigests = null;
// really just trying to save the allocation of these two arrays. Worth it???
msg_header_buf = new byte[CL_MSG_HEADER_LEN + AS_MSG_HEADER_LEN];
msg_write_len = 0;
msg_write_buf = null;
msg_read_buf = null;
}
// Concurrent linked queues are slow to find the size, and
static java.util.concurrent.atomic.AtomicInteger CLBufferQueueSize =
new java.util.concurrent.atomic.AtomicInteger(0);
static java.util.concurrent.ConcurrentLinkedQueue<CLBuffer> CLBufferQueue =
new java.util.concurrent.ConcurrentLinkedQueue<CLBuffer> ();
public static CLBuffer create()
{
CLBuffer b = CLBufferQueue.poll();
if (b == null) {
b = new CLBuffer();
}
else {
CLBufferQueueSize.decrementAndGet();
}
return(b);
}
public void destroy()
{
if (CLBufferQueueSize.get() < 50) {
this.reset();
CLBufferQueue.add(this);
CLBufferQueueSize.decrementAndGet();
}
}
//
// Sets singleton initial values
// Function must be expanded for secondary references
public void set_required(String namespace, String table, Object key, ClWriteOptions cl_wp)
{
this.ns = namespace;
this.set = table;
this.key = key;
this.cl_wp = cl_wp;
}
//
// Set digest values
public void set_required(String namespace, byte[] digest , ClWriteOptions cl_wp)
{
this.ns = namespace;
this.digest = digest;
this.cl_wp = cl_wp;
}
// on a read request - read this bin too
public void addRead(String bin)
{
if (bin == null) {
this.readAll();
} else {
if (readBins == null) {
readBins = new ArrayList<String>(5);
}
readBins.add(bin);
}
}
// NB: it could be interesting to do a clone. The documentation
// should state what happens to the collection. It would be even faster
// to simply hand off the collection to our interface, if they're not using
// it or know it won't change (like, it's a final), so we could have an api
// for that
public void addRead(Collection<String> bins)
{
if (bins == null) {
this.readAll();
} else {
if (readBins == null)
readBins = bins;
else
readBins.addAll(bins);
}
}
public void addRead(String[] bins)
{
if (bins == null) {
this.readAll();
} else {
if (readBins == null) {
readBins = new ArrayList<String>(bins.length);
}
for (String s : bins) {
readBins.add(s);
}
}
}
public void readAll()
{
// if we previously had explict bins, throw them away
if (readBins != null)
readBins = null;
readAll = true;
}
public void readIterate(ScanCallback sc, Object ob)
{
if (readBins != null)
readBins = null;
readIterate = true;
callback = sc;
scanObject = ob;
}
public void readNoBindata()
{
readNoBindata = true;
}
public boolean isReadIterate()
{
return(readIterate);
}
// on a write request - write this bin too
public void addWrite(Collection<ClBin> bins)
{
if (writeBins == null)
writeBins = bins;
else
writeBins.addAll(bins);
partition_type = CLConnectionManager.PartitionType.WRITE;
}
// on a write request - write these bins too
public void addWrite(HashMap<String, Object> bins)
{
if (writeBins == null)
writeBins = new ArrayList<ClBin>(bins.size());
for (String key : bins.keySet()) {
ClBin asc = new ClBin();
asc.name = key;
asc.value = bins.get(key);
writeBins.add(asc);
}
partition_type = CLConnectionManager.PartitionType.WRITE;
}
public void addWrite(String bin, Object o)
{
if (writeBins == null)
writeBins = new ArrayList<ClBin>(5);
ClBin asc = new ClBin();
asc.name = bin;
asc.value = o;
writeBins.add(asc);
partition_type = CLConnectionManager.PartitionType.WRITE;
}
// on a write request - write this bin too
public void addAdd(Collection<ClBin> bins)
{
if (addBins == null)
addBins = bins;
else
addBins.addAll(bins);
partition_type = CLConnectionManager.PartitionType.WRITE;
}
// on a write request - write these bins too
public void addAdd(HashMap<String, Object> bins)
{
if (addBins == null)
addBins = new ArrayList<ClBin>(bins.size());
for (String key : bins.keySet()) {
ClBin asc = new ClBin();
asc.name = key;
asc.value = bins.get(key);
addBins.add(asc);
}
partition_type = CLConnectionManager.PartitionType.WRITE;
}
public void addAdd(String bin, Object o)
{
if (addBins == null)
addBins = new ArrayList<ClBin>(5);
ClBin asc = new ClBin();
asc.name = bin;
asc.value = o;
addBins.add(asc);
partition_type = CLConnectionManager.PartitionType.WRITE;
}
// on a write request - write this bin too
public void addAppend(Collection<ClBin> bins)
{
if (appendBins == null)
appendBins = bins;
else
appendBins.addAll(bins);
partition_type = CLConnectionManager.PartitionType.WRITE;
}
// on a write request - write these bins too
public void addAppend(HashMap<String, Object> bins)
{
if (appendBins == null)
appendBins = new ArrayList<ClBin>(bins.size());
for (String key : bins.keySet()) {
ClBin asc = new ClBin();
asc.name = key;
asc.value = bins.get(key);
appendBins.add(asc);
}
partition_type = CLConnectionManager.PartitionType.WRITE;
}
public void addAppend(String bin, Object o)
{
if (appendBins == null)
appendBins = new ArrayList<ClBin>(5);
ClBin asc = new ClBin();
asc.name = bin;
asc.value = o;
appendBins.add(asc);
partition_type = CLConnectionManager.PartitionType.WRITE;
}
public void setDelete() {
this.delete = true;
partition_type = CLConnectionManager.PartitionType.WRITE;
}
public void setBatchRead(ArrayList<Object> keys, ArrayList<byte[]> alldigests) {
this.batchread = true;
this.allkeys = keys;
this.alldigests = alldigests;
}
public boolean isBatchRead() {
return this.batchread;
}
//
// Do all the buffer preparation that makes it ready to read or write
// (Should this be internal? almost doesn't matter, since the entire interface
// is internal)
public void prepare() throws SerializeException {
byte[][] jblobs = null;
int sz_estimate = 30;
if (readBins != null)
sz_estimate += readBins.size() * 50;
if (writeBins != null) {
jblobs = new byte[writeBins.size()][];
for (int i = 0; i < writeBins.size(); i++)
jblobs[i] = null;
int i = 0;
for (ClBin col : writeBins) {
int op_estimate_sz = makeOpEstimate(col.value);
if (op_estimate_sz == 0) {
jblobs[i] = objectToByteArray(col.value);
op_estimate_sz = 8 + 32 + jblobs[i].length;
}
sz_estimate += op_estimate_sz;
i++;
}
}
if (addBins != null) {
jblobs = new byte[addBins.size()][];
for (int i = 0; i < addBins.size(); i++)
jblobs[i] = null;
int i = 0;
for (ClBin col : addBins) {
int op_estimate_sz = makeOpEstimate(col.value);
if (op_estimate_sz == 0) {
jblobs[i] = objectToByteArray(col.value);
op_estimate_sz = 8 + 32 + jblobs[i].length;
}
sz_estimate += op_estimate_sz;
i++;
}
}
if (appendBins != null) {
jblobs = new byte[appendBins.size()][];
for (int i = 0; i < appendBins.size(); i++)
jblobs[i] = null;
int i = 0;
for (ClBin col : appendBins) {
int op_estimate_sz = makeOpEstimate(col.value);
if (op_estimate_sz == 0) {
jblobs[i] = objectToByteArray(col.value);
op_estimate_sz = 8 + 32 + jblobs[i].length;
}
sz_estimate += op_estimate_sz;
i++;
}
}
int fields_sz = makeFieldsEstimate();
sz_estimate += fields_sz;
if (sz_estimate < 2 * 1024) sz_estimate = 2 * 1024;
// CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"size estimate final "+sz_estimate);
if ((msg_write_buf == null) ||
(msg_write_buf.length < sz_estimate)) {
msg_write_buf = new byte[sz_estimate];
}
int offset = 30;
offset += makeFields(msg_write_buf, offset);
byte info1 = 0;
byte info2 = 0;
byte info3 = 0;
if (readBins != null) {
for (String s : readBins) {
offset += makeOp(OP_READ, s, null, msg_write_buf, offset, null);
}
info1 |= INFO1_READ;
}
else if (readAll) {
info1 |= INFO1_GET_ALL;
info1 |= INFO1_READ;
} else if (readIterate) {
info1 |= INFO1_READ;
}
if (readNoBindata) {
info1 |= INFO1_NOBINDATA;
}
if (writeBins != null) {
int i = 0;
for (ClBin col : writeBins) {
offset += makeOp(OP_WRITE, col.name, col.value, msg_write_buf, offset, jblobs[i]);
i++;
}
info2 |= INFO2_WRITE;
}
if (addBins != null) {
int i = 0;
for (ClBin col : addBins) {
offset += makeOp(OP_ADD, col.name, col.value, msg_write_buf, offset, jblobs[i]);
i++;
}
info2 |= INFO2_WRITE;
}
if (appendBins != null) {
int i = 0;
for (ClBin col : appendBins) {
offset += makeOp(OP_APPEND, col.name, col.value, msg_write_buf, offset, jblobs[i]);
i++;
}
info2 |= INFO2_WRITE;
}
msg_write_len = offset;
int generation = 0;
if (cl_wp != null) {
if (cl_wp.use_generation) {
info2 |= INFO2_GENERATION;
generation = cl_wp.generation;
}
else if (cl_wp.use_generation_gt) {
info2 |= INFO2_GENERATION_GT;
generation = cl_wp.generation;
}
else if (cl_wp.use_generation_dup) {
info2 |= INFO2_GENERATION_DUP;
generation = cl_wp.generation;
}
else if (cl_wp.unique) {
info2 |= INFO2_WRITE_UNIQUE;
}
}
if (delete) {
info2 |= INFO2_WRITE | INFO2_DELETE;
}
// Make the actual header
long sz = (msg_write_len - 8) | (CL_MSG_VERSION << 56) | (AS_MSG_TYPE << 48);
set_htonll(sz, msg_write_buf, 0);
msg_write_buf[8] = AS_MSG_HEADER_LEN; // 22!
msg_write_buf[9] = info1;
msg_write_buf[10] = info2;
msg_write_buf[11] = info3;
msg_write_buf[12] = 0; // unused
msg_write_buf[13] = 0; // clear the result code
set_htonl(generation, msg_write_buf, 14);
// record ttl
if (cl_wp != null) {
set_htonl(cl_wp.expiration, msg_write_buf, 18);
}
else {
msg_write_buf[18] = msg_write_buf[19] = msg_write_buf[20] = msg_write_buf[21] = 0;
}
// transaction ttl - init to reasonable value
msg_write_buf[22] = msg_write_buf[23] = msg_write_buf[24] = msg_write_buf[25] = 0;
// number of fields: if digest n_fileds=2 (for namespace and digest)
// else one each for namespace, set and key
int n_fields = 0;
if (this.digest != null) {
n_fields = 2;
} else {
n_fields = ((this.key != null)?1:0) + ((this.set != null)?1:0) + ((this.ns != null)?1:0);
}
set_htons(n_fields, msg_write_buf, 26);
int n_ops = 0;
if (readBins != null) n_ops += readBins.size();
if (writeBins != null) n_ops += writeBins.size();
if (addBins != null) n_ops += addBins.size();
if (appendBins != null) n_ops += appendBins.size();
set_htons(n_ops,msg_write_buf,28);
// DEBUG
// dump_buf("write msg ",msg_write_buf,(int)msg_write_len);
}
// Updates the timeout field..
// uses the 'start time' that was captured when the buffer was created
// and the policies in the ClOptions
public void setTimeout(int ms_remaining) {
set_htonl(ms_remaining, msg_write_buf, 22);
}
// In order to profile, pick this apart into multiple methods
private ClResult parseResult(byte[] msg, int len, int n_ops, int n_fields, int resultCode, int generation) throws SerializeException
{
ClResult r = new ClResult();
r.generation = generation; // yo! sup with this???
switch (resultCode) {
case 0:
r.resultCode = ClResultCode.OK;
break;
case 1:
default:
r.resultCode = ClResultCode.SERVER_ERROR;
break;
case 2:
r.resultCode = ClResultCode.KEY_NOT_FOUND_ERROR;
break;
case 3:
r.resultCode = ClResultCode.GENERATION_ERROR;
break;
case 4:
r.resultCode = ClResultCode.PARAMETER_ERROR;
break;
case 5:
r.resultCode = ClResultCode.KEY_EXISTS_ERROR;
break;
case 6:
r.resultCode = ClResultCode.BIN_EXISTS_ERROR;
break;
}
// if there are headers, we'll have to skip over them,
// but for now, how about just alerting if there are any?
if (n_fields != 0) {
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"recv message: there are fields set, unexpected, failure");
return(null);
}
int offset = 0;
for (int i=0 ; i<n_ops ; i++) {
int op_sz = get_ntohl(msg, offset);
byte particle_type = msg[offset+5];
byte version = msg[offset+6];
byte name_sz = msg[offset+7];
String name = utf8ToString(msg, offset+8, name_sz);
offset += 4 + 4 + name_sz;
// System.out.printf("op size %d op %d part-type %d name_sz %d name %s\n",
// op_sz, op, particle_type, name_sz, name);
int particleBytes_sz = (int) (op_sz - (4 + name_sz));
Object value = null;
try {
value = bytesToParticle(particle_type, msg, offset, particleBytes_sz);
offset += particleBytes_sz;
}
catch (SerializeException e) {
if (key != null) {
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO," de-serialize exception: namespace "+ns+" set "+set+" key "+key.toString() );
}
else {
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO," de-serialize exception: namespace "+ns+" set "+set);
}
throw e;
}
// place this nvp somewhere cool
// I detest ArrayList. I should be able to set() and have it auto-grow.
Map<String,Object> vmap = null;
if (version > 0 || r.results_dup != null) {
// CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO," version greater than 0? srsly? "+version);
if (r.results_dup == null) {
r.results_dup = new ArrayList<Map<String,Object>>(4);
r.results_dup.add(r.results);
r.results = null;
for (int j=0;j<version;j++) r.results_dup.add(null);
} else {
for (int j=r.results_dup.size(); j<version+1;j++) r.results_dup.add(null);
}
vmap = r.results_dup.get(version);
if (vmap == null) {
vmap = new HashMap<String,Object>();
r.results_dup.set(version,vmap);
}
}
else {
if (r.results == null)
r.results = new HashMap<String,Object>();
vmap = r.results;
}
vmap.put(name, value);
}
// just in case there were holes in the versin number space
if (r.results_dup != null)
while ( r.results_dup.remove(null) );
return(r);
}
// Method to parse multiple results in case of scan API
private ClResult parseMultiResult(byte[] msg, int n_bytes) throws SerializeException
{
ClResult r = null;
byte [] digest = null;
String ns = null;
int offset = 0;
do
{
r = new ClResult(ClResultCode.OK);
byte h_len = msg[offset];
byte info1 = msg[offset+1];
byte info2 = msg[offset+2];
byte info3 = msg[offset+3];
byte unused = msg[offset+4];
byte resultCode = msg[offset+5];
int generation = get_ntohl(msg, offset + 6);
int record_ttl = get_ntohl(msg, offset + 10);
int transaction_ttl = get_ntohl(msg, offset + 14);
int n_fields = get_ntohs(msg, offset + 18);
int n_ops = get_ntohs(msg, offset + 20);
offset += 22;
if (h_len != 22)
{
r.resultCode = ClResultCode.SERVER_ERROR;
return (r);
}
// check for last message
if ((info3 & INFO3_LAST) == INFO3_LAST)
{
gotlastmsg = true;
return (r);
}
r.generation = generation;
setResultCode(r, resultCode);
if (r.resultCode != ClResultCode.OK)
{
return (r);
}
// Parce the fields
if (n_fields > 0) {
// grab digest, namespace might be in there too
for (int i = 0; i < n_fields; i++) {
int field_sz = (int)get_ntohl(msg, offset);
byte type = msg[offset + 4];
if (type == FIELD_TYPE_DIGEST_RIPE) {
digest = new byte[field_sz - 1];
System.arraycopy(msg, offset + 5, digest, 0, field_sz - 1);
} else if (type == FIELD_TYPE_NAMESPACE) {
ns = new String(msg, offset + 5, field_sz - 1);
}
// do something with the data? there's a namespace here.
offset += 4 + field_sz;
}
}
// Parse ops
try {
// Pass continueOnError as true when calling from scan
r.corruptedData = false; //Assume that there is no corruption to start with
offset += parseOps(r, msg, offset, n_ops, true);
}
catch (SerializeException e) {
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"de-serialize exception during scan. Digest="+byteArrayToHexString(digest));
throw e;
}
// If data is corrupted, print error and continue scan
if (r.corruptedData) {
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"de-serialize exception during scan. Digest="+byteArrayToHexString(digest));
continue;
}
//Call the callback function with the values
callback.scanCallback(ns, digest, r.results, generation, record_ttl, scanObject);
} while (offset < n_bytes);
return(r);
}
// Special prepare function to read batch of digests
public void batchPrepare() throws SerializeException {
int sz_estimate = 30;
if (readBins != null) {
sz_estimate += readBins.size() * 50;
}
int fields_sz = makeFieldsEstimate();
sz_estimate += fields_sz;
//we should also consider the size of all the digests for the fields
sz_estimate += alldigests.size() * DIGEST_SIZE;
if (sz_estimate < 2 * 1024) {
sz_estimate = 2 * 1024;
}
if ((msg_write_buf == null) || (msg_write_buf.length < sz_estimate)) {
msg_write_buf = new byte[sz_estimate];
}
int offset = 30;
offset += makeBatchFields(msg_write_buf, offset, alldigests);
byte info1 = 0;
byte info2 = 0;
byte info3 = 0;
if (readBins != null) {
for (String s : readBins) {
offset += makeOp(OP_READ, s, null, msg_write_buf, offset, null);
}
info1 |= INFO1_READ;
}
else {
if (readAll) {
info1 |= INFO1_GET_ALL;
info1 |= INFO1_READ;
}
}
msg_write_len = offset;
int generation = 0;
// We do not currently support writeparameters
assert (cl_wp == null) : cl_wp;
// Make the actual header
long sz = (msg_write_len - 8) | (CL_MSG_VERSION << 56) | (AS_MSG_TYPE << 48);
set_htonll(sz, msg_write_buf, 0);
msg_write_buf[8] = AS_MSG_HEADER_LEN; // 22!
msg_write_buf[9] = info1;
msg_write_buf[10] = info2;
msg_write_buf[11] = info3;
msg_write_buf[12] = 0; // unused
msg_write_buf[13] = 0; // clear the result code
set_htonl(generation, msg_write_buf, 14);
// record ttl
msg_write_buf[18] = msg_write_buf[19] = msg_write_buf[20] = msg_write_buf[21] = 0;
// transaction ttl
msg_write_buf[22] = msg_write_buf[23] = msg_write_buf[24] = msg_write_buf[25] = 0;
// number of fields=2: Namespace and digests
set_htons(2, msg_write_buf, 26);
int n_ops = 0;
if (readBins != null) {
n_ops += readBins.size();
}
set_htons(n_ops,msg_write_buf,28);
}
//Sets the corresponding result code in the result object
private void setResultCode(ClResult r, int resultCode)
{
switch (resultCode) {
case 0:
r.resultCode = ClResultCode.OK;
break;
case 1:
default:
r.resultCode = ClResultCode.SERVER_ERROR;
break;
case 2:
r.resultCode = ClResultCode.KEY_NOT_FOUND_ERROR;
break;
case 3:
r.resultCode = ClResultCode.GENERATION_ERROR;
break;
case 4:
r.resultCode = ClResultCode.PARAMETER_ERROR;
break;
case 5:
r.resultCode = ClResultCode.KEY_EXISTS_ERROR;
break;
case 6:
r.resultCode = ClResultCode.BIN_EXISTS_ERROR;
break;
}
}
//Parses the given byte buffer and populate the result object
//Retruns the number of bytes that were parsed from the given buffer
private int parseOps(ClResult r, byte[] buf, int opsoffset, int n_ops, boolean continueOnError) throws SerializeException
{
int initialoffset = opsoffset;
boolean skipbin=false;
for (int i=0 ; i<n_ops ; i++) {
int op_sz = get_ntohl(buf, opsoffset);
byte op = buf[opsoffset + 4];
byte op_type = buf[opsoffset + 5];
byte version = buf[opsoffset + 6];
byte name_sz = buf[opsoffset + 7];
String name = utf8ToString(buf, opsoffset + 8, name_sz);
opsoffset += 4 + 4 + name_sz;
//Currently, the batch command returns all the bins even if a subset of
//the bins are requested. So, we have to filter it on the client side.
//We have to filter if the client does not want to read all bins
skipbin = false;
if (!readAll && (readBins != null) && !readBins.contains(name)) {
//System.out.println("skipping the bin "+name);
skipbin = true;;
}
int particleBytes_sz = (int) (op_sz - (4 + name_sz));
Object value = null;
try {
value = bytesToParticle(op_type, buf, opsoffset, particleBytes_sz);
}
catch (SerializeException e) {
// In case of call from scan, continueOnError is set, so avoid throwing exception and set that data is corrupted
if (continueOnError) {
r.corruptedData = true;
} else {
//Key may not be set in some cases like the scan
if (key != null) {
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO," de-serialize exception: namespace "+ns+" set "+set+" key "+key.toString() );
}
else {
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO," de-serialize exception: namespace "+ns+" set "+set);
}
throw e;
}
}
opsoffset += particleBytes_sz;
// If data is corrupted, continue without processing the result
if (r.corruptedData) {
continue;
}
if (!skipbin) {
// place this nvp somewhere cool
// I detest ArrayList. I should be able to set() and have it auto-grow.
Map<String,Object> vmap = null;
if (version > 0 || r.results_dup != null) {
// CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO," version greater than 0? srsly? "+version);
if (r.results_dup == null) {
r.results_dup = new ArrayList<Map<String,Object>>(4);
r.results_dup.add(r.results);
r.results = null;
for (int j=0;j<version;j++) r.results_dup.add(null);
} else {
for (int j=r.results_dup.size(); j<version+1;j++) r.results_dup.add(null);
}
vmap = r.results_dup.get(version);
if (vmap == null) {
vmap = new HashMap<String,Object>();
r.results_dup.set(version,vmap);
}
}
else {
if (r.results == null)
r.results = new HashMap<String,Object>();
vmap = r.results;
}
vmap.put(name, value);
}
}
// just in case there were holes in the versin number space
if (r.results_dup != null)
{
while ( r.results_dup.remove(null) );
}
return (opsoffset - initialoffset);
}
// Given a contigious byte array of all the respose messages this function
// will parse each message and create a ClResult object out of it and store
// them in an array which is passed by the caller. The result will be placed
// in the position corresponding to the position of the key i.e The index
// of the key in the registed arraylist and that of its corresponding result
// in the clresult array that is returned will be the same. If there is no
// data corresponding to a key in the response from the server, its clresult
// will be set to null. The array of clresult objects will be a field in the
// ClResult object that is returned. The caller has to use the array.
private void parseBatchResult(ClResult r_main, byte[] buf, int buflen) throws SerializeException
{
int msgoffset, fieldoffset, opsoffset, keyindex;
byte[] digest = new byte[DIGEST_SIZE];
String ns = null;
ClResult r = null;
//Parse each message response and add it to th clresult array
msgoffset = 0;
while (msgoffset < buflen) {
keyindex = -2; //will be used to know if we got a digest in this msg or not
r = null;
byte h_len = buf[msgoffset + 0];
byte info1 = buf[msgoffset + 1];
byte info2 = buf[msgoffset + 2];
byte info3 = buf[msgoffset + 3];
byte unused = buf[msgoffset + 4];
int resultCode = buf[msgoffset + 5];
int generation = get_ntohl(buf, msgoffset + 6);
int record_ttl = get_ntohl(buf, msgoffset + 10);
int txn_ttl = get_ntohl(buf, msgoffset + 14);
int n_fields = get_ntohs(buf, msgoffset + 18);
int n_ops = get_ntohs(buf, msgoffset + 20);
if (h_len != AS_MSG_HEADER_LEN) {
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"read header batch: unexpected header size");
}
// If this is the end marker of the response, do not proceed further
if ((info3 & INFO3_LAST) == INFO3_LAST) {
gotlastmsg = true;
return;
}
//Response msg for batch will have fields.
fieldoffset = msgoffset + AS_MSG_HEADER_LEN;
for (int i=0; i<n_fields; i++) {
int fieldlen = get_ntohl(buf, fieldoffset);
int fieldtype = buf[fieldoffset + 4];
if (fieldtype == FIELD_TYPE_KEY) {
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"read message batch: unexpectedly found key");
}
else if (fieldtype == FIELD_TYPE_DIGEST_RIPE) {
for (int j=0; j<DIGEST_SIZE; j++) {
digest[j] = buf[fieldoffset + 5 + j];
}
// Find the index of the corresponding key for the digest
// indexOf() cannot be used for byte arrays. So, we do it ourselves
int k;
Iterator<byte[]> it;
for (it=alldigests.iterator(), k=0 ; it.hasNext(); k++ ) {
byte[] querydigest = it.next();
if (Arrays.equals(digest, querydigest))
{
keyindex = k;
break;
}
}
}
else if (fieldtype == FIELD_TYPE_NAMESPACE) {
//Remember, one byte is used for type out of field
ns = new String(buf, fieldoffset + 5, fieldlen-1);
}
//Next field is at (4bytes of filedlen value + field length)
fieldoffset += 4 + fieldlen;
}
if (keyindex == -2) {
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"read message batch: Did not get any digest in this msg, aborting midway");
return;
}
else if (keyindex == -1) {
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"read message batch: Got an unexpected digest, aborting midway");
return;
}
else
{
r_main.clresult_array[keyindex] = new ClResult();
r = r_main.clresult_array[keyindex];
}
r.generation = generation;
setResultCode(r, resultCode);
//Now read the ops
opsoffset = fieldoffset;
// Pass continueOnError as false when calling from batch
opsoffset += parseOps(r, buf, opsoffset, n_ops, false);
//Move on to the next msg
msgoffset = opsoffset;
}
return;
}
public ClResult get_and_parse_batch(InputStream is) throws IOException, SerializeException
{
ClResult r_main = new ClResult();
int numkeys = allkeys.size();
r_main.clresult_array = new ClResult[numkeys];
for (int i=0; i<numkeys ; i++) {
r_main.clresult_array[i] = null;
}
gotlastmsg = false;
do {
int rlen = 0;
while (rlen < CL_MSG_HEADER_LEN) {
int read_rv = is.read(msg_header_buf, rlen, CL_MSG_HEADER_LEN - rlen);
if (read_rv < 0) {
throw new java.net.SocketException("socket closed");
}
rlen += read_rv;
}
long sz = get_ntohll(msg_header_buf, 0);
byte cl_version = (byte) (((int)(sz >> 56)) & 0xff);
byte cl_type = (byte) (((int)(sz >> 48)) & 0xff);
// In case of batch result, we will get multiple msgs as response
// Get the size of all the msgs together. Each msg will have its
// own header and body.
int allmsgs_bytes = ((int) (sz & 0xFFFFFFFFFFFFL));
if (cl_version != CL_MSG_VERSION) {
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"read header batch: incorrect version");
}
if (cl_type != AS_MSG_TYPE) {
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"read header batch: incorrect message type");
}
//Read all the messages along with their headers and store in a buffer
int alloc_bytes = 2 * 1024;
if (alloc_bytes < allmsgs_bytes) {
alloc_bytes = allmsgs_bytes;
}
if ((msg_read_buf == null) || (msg_read_buf.length < alloc_bytes)) {
msg_read_buf = new byte[alloc_bytes];
}
rlen = 0;
while (rlen < allmsgs_bytes) {
int read_rv = is.read(msg_read_buf, rlen, allmsgs_bytes - rlen);
if (read_rv < 0) {
throw new java.net.SocketException("socket closed");
}
rlen += read_rv;
}
//dump_buf("batch response header", msg_header_buf, CL_MSG_HEADER_LEN);
//dump_buf("batch response msgs", msg_read_buf, allmsgs_bytes);
//gotlastmsg is a class variable and will be modified by this function.
parseBatchResult(r_main, msg_read_buf, allmsgs_bytes);
} while (!gotlastmsg);
return r_main;
}
// Get and parse method for scan API
public ClResult multi_get_and_parse(InputStream is) throws IOException, SerializeException
{
ClResult r = new ClResult(ClResultCode.OK);
gotlastmsg = false;
do {
int rlen = 0;
while (rlen < CL_MSG_HEADER_LEN) {
int read_rv = is.read(msg_header_buf, rlen, CL_MSG_HEADER_LEN-rlen);
if (read_rv < 0) {
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"scan: header connection from server lost." );
throw new java.net.SocketException("socket closed on scan header read");
}
else if (read_rv == 0) {
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"scan: Got zero bytes." );
}
rlen += read_rv;
}
long sz = get_ntohll(msg_header_buf, 0);
byte cl_version = (byte) (((int)(sz >> 56)) & 0xff);
byte cl_type = (byte) (((int)(sz >> 48)) & 0xff);
int n_bytes = (int)(sz & 0xFFFFFFFFFFFFL);
if (cl_version != CL_MSG_VERSION) {
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"read header: incorrect version, aborting");
}
if (cl_type != AS_MSG_TYPE) {
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"read header: incorrect message type, aborting receive");
}
if (n_bytes != 0) {
if (msg_read_buf == null) {
msg_read_buf = new byte[n_bytes < (2 * 1024) ? (2 * 1024) : n_bytes];
}
if (msg_read_buf.length < n_bytes) {
msg_read_buf = new byte[n_bytes];
}
// readit
rlen = 0;
while (rlen < n_bytes) {
int read_rv = is.read(msg_read_buf, rlen, n_bytes - rlen);
if (read_rv < 0) {
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"scan: read connection from server lost." );
throw new java.net.SocketException("socket closed on scan content read");
}
else if (read_rv == 0) {
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"scan: Got zero bytes." );
}
rlen += read_rv;
}
}
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"scan: processing "+n_bytes +" bytes from CL");
r = parseMultiResult(msg_read_buf, n_bytes);
if (r.resultCode != ClResultCode.OK)
{
return (r);
}
} while (!gotlastmsg);
return (r);
}
public ClResult get_and_parse(InputStream is) throws IOException, SerializeException
{
int rlen = 0;
while (rlen < CL_MSG_HEADER_LEN + AS_MSG_HEADER_LEN) {
int read_rv = is.read(msg_header_buf);
if (read_rv < 0) {
throw new java.net.SocketException("socket closed");
}
rlen += read_rv;
}
// Debug
// dump_buf("read header", msg_header_buf, 0);
// A number of these are commented out because we just don't care enough to read
// that section of the header. If we do care, uncomment and check!
long sz = get_ntohll(msg_header_buf, 0);
byte cl_version = (byte) (((int)(sz >> 56)) & 0xff);
byte cl_type = (byte) (((int)(sz >> 48)) & 0xff);
byte h_len = msg_header_buf[8];
// byte info1 = msg_header_buf[9];
// byte info2 = msg_header_buf[10];
// byte info3 = msg_header_buf[11];
// byte unused = msg_header_buf[12];
int resultCode = msg_header_buf[13];
int generation = get_ntohl(msg_header_buf,14);
// int record_ttl = get_ntohl(msg_header_buf,18);
// int transaction_ttl = get_ntohl(msg_header_buf, 22);
int n_fields = get_ntohs(msg_header_buf,26); // almost certainly 0
int n_ops = get_ntohs(msg_header_buf,28);
int n_bytes = ((int) (sz & 0xFFFFFFFFFFFFL)) - h_len;
if (cl_version != CL_MSG_VERSION) {
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"read header: incorrect version, aborting");
}
if (cl_type != AS_MSG_TYPE) {
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"read header: incorrect message type, aborting receive");
}
if (h_len != AS_MSG_HEADER_LEN) {
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"read header: unexpected header size, aborting");
}
// System.out.printf("read header: version %d hlen %d result %d nops %d nbytes %d\n",
// cl_version, h_len, resultCode, n_ops, n_bytes);
// read! more - allocate a byte buffer for the whole steenking thing
if (n_bytes != 0) {
if (msg_read_buf == null) {
msg_read_buf = new byte[n_bytes < (2 * 1024) ? (2 * 1024) : n_bytes];
}
if (msg_read_buf.length < n_bytes) {
msg_read_buf = new byte[n_bytes];
}
// readit
rlen = 0;
while (rlen < n_bytes) {
int read_rv = is.read(msg_read_buf, rlen, n_bytes - rlen);
if (read_rv < 0) throw new java.net.SocketException("socket closed");
rlen += read_rv;
}
// dump_buf("read msg body", msg_read_buf, n_bytes);
}
// parseit
ClResult r = parseResult(msg_read_buf, n_bytes, n_ops, n_fields, resultCode, generation);
return(r);
}
final static int PARTICLE_TYPE_NULL = 0;
final static int PARTICLE_TYPE_INTEGER = 1;
final static int PARTICLE_TYPE_BIGNUM = 2;
final static int PARTICLE_TYPE_STRING = 3;
final static int PARTICLE_TYPE_BLOB = 4;
final static int PARTICLE_TYPE_TIMESTAMP = 5;
final static int PARTICLE_TYPE_DIGEST = 6;
final static int PARTICLE_TYPE_JBLOB = 7;
final static int PARTICLE_TYPE_CSHARP_BLOB = 8;
final static int PARTICLE_TYPE_PYTHON_BLOB = 9;
final static int PARTICLE_TYPE_RUBY_BLOB = 10;
final static int PARTICLE_TYPE_PHP_BLOB = 11;
final static int PARTICLE_TYPE_ERLANG_BLOB = 12;
final static int PARTICLE_TYPE_SEGMENT_POINTER = 13;
final static int PARTICLE_TYPE_LIST = 14;
final static int PARTICLE_TYPE_DICT = 15;
private static class ParticleBytesReturn {
int type;
int size; // bytes copied
};
// Copy object into buffer, return the type and the new offset
// IOException is thrown when the class can't be serialized
// Parameter: 'java_serialized_bytes' is passed in when we've already figured out the object is requires java
// serialization, and
@SuppressWarnings("unchecked")
private ParticleBytesReturn objectToParticleBytes(Object o, byte[] buf, int offset, byte[] java_serialized_bytes) throws SerializeException
{
ParticleBytesReturn r = new ParticleBytesReturn();
if (o == null) {
r.type = PARTICLE_TYPE_NULL;
r.size = 0;
return(r);
}
// shortcut: if java_serialized bytes are in, just use them
if (java_serialized_bytes != null) {
r.type = PARTICLE_TYPE_JBLOB;
System.arraycopy(java_serialized_bytes, 0, buf, offset, java_serialized_bytes.length);
r.size = java_serialized_bytes.length;
return(r);
}
Class c = o.getClass();
// people who use byte arrays care more about speed...
if (g_byteArrayClass.isAssignableFrom( c ) == true) {
// I'd like to support byte[] as blob, not quite sure how to do it yet
r.type = PARTICLE_TYPE_BLOB;
byte[] b = (byte[]) o;
System.arraycopy(b, 0, buf, offset, b.length);
r.size = b.length;
}
else if (g_stringClass.isAssignableFrom( c )==true ) {
r.type = PARTICLE_TYPE_STRING;
String s = (String) o;
byte[] value;
try {
value = s.getBytes("UTF8");
} catch (UnsupportedEncodingException e) {
// If you can't encode a UTF8 string you're in sad, sad shape
throw new SerializeException();
}
System.arraycopy(value, 0, buf, offset, value.length);
r.size = value.length;
}
else if ((g_intClass.isAssignableFrom( c )==true) ||
(g_longClass.isAssignableFrom( c )==true)) {
/*
* If it is of integer type(4bytes) or long type(8 bytes),
* when sending to the server always send in singned 64bit
* format (2's complement method for -ve numbers). This will
* make it easy to store and retrive. Single byte +ve values
* are an exception to this rule which use single byte.
*/
r.type = PARTICLE_TYPE_INTEGER;
// Both Interger & Long class has longValue() method.
// Typecast the object to Number which is a superclass.
// Polymorphism will take care of the rest.
long i = ((Number)o).longValue();
if ((i >= 0) && (i < 128)) {
buf[offset] = (byte) (i & 0xff);
r.size = 1;
}
else {
set_htonll(i, buf, offset);
r.size = 8;
}
}
// everything else gets serialized with the java serializer
// although we expect to not his this path often
else {
r.type = PARTICLE_TYPE_JBLOB;
r.size = objectToByteArray(o, buf, offset); // use serialization to make a blob
}
return(r);
}
private static Object parseList(byte[] buf, int offset, int len) throws SerializeException {
int limit = offset + len;
int n_items = (int) get_ntohl(buf, offset);
offset += 4;
ArrayList<Object> r = new ArrayList<Object>(n_items);
while (offset < limit)
{
int sz = (int) get_ntohl(buf, offset);
offset += 4;
int type = buf[offset];
offset++;
try {
r.add(
bytesToParticle(type, buf, offset, sz)
);
offset += sz;
} catch (SerializeException e) {
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO," de-serialize exception in parseList");
throw e;
}
}
return(r);
}
private static Object parseDict(byte[] buf, int offset, int len) throws SerializeException {
Object key;
Object value;
int limit = offset + len;
int n_items = (int) get_ntohl(buf, offset);
offset += 4;
HashMap<Object, Object> d = new HashMap<Object, Object>(n_items);
while (offset < limit)
{
// read out the key
int sz = (int) get_ntohl(buf, offset);
offset += 4;
int type = buf[offset];
offset++;
try {
key = bytesToParticle(type, buf, offset, len);
offset += sz;
} catch (SerializeException e) {
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO," de-serialize exception in parseDict");
throw e;
}
// read out the value
sz = (int) get_ntohl(buf, offset);
offset += 4;
type = buf[offset];
offset++;
try {
value = bytesToParticle(type, buf, offset, len);
offset += sz;
} catch (SerializeException e) {
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO," de-serialize exception in parseDict: key "+key.toString() );
throw e;
}
d.put(key, value);
}
return (d);
}
private static Object bytesToParticle(int type, byte[] buf, int offset, int len) throws SerializeException
{
Object r;
if (type == PARTICLE_TYPE_STRING) {
r = utf8ToString(buf, offset, len);
}
else if (type == PARTICLE_TYPE_INTEGER) {
r = bytesToInteger(buf, offset, len);
}
else if (type == PARTICLE_TYPE_BLOB) {
r = Arrays.copyOfRange(buf, offset, offset+len);
}
else if (type == PARTICLE_TYPE_JBLOB) {
r = byteArrayToObject(buf, offset, len);
}
else if (type == PARTICLE_TYPE_LIST) {
r = parseList(buf, offset, len);
}
else if (type == PARTICLE_TYPE_DICT) {
r = parseDict(buf, offset, len);
}
else {
//CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"bytesToParticle: Unsupported type "+type);
r = null;
}
return(r);
}
// Need a nice private function to serialize objects into byte buffers
// This is the hideously general purpose way; very slow; probably should use
// some kind of special purpose for strings. There's lots of stuff out there for
// serializing types
public static Object byteBufferToObject(ByteBuffer b) throws Exception
{
ObjectInputStream oistream;
ByteArrayInputStream bastream;
// if we can get the backing store, use it directly
if (b.hasArray()) {
byte[] barray = b.array();
int boffset = b.arrayOffset();
bastream = new ByteArrayInputStream(barray,boffset + b.position() , boffset+b.remaining() );
}
// otherwise copy
else {
int len = b.remaining();
byte[] barray = new byte[len];
b.get(barray);
bastream = new ByteArrayInputStream(barray);
}
oistream = new ObjectInputStream(bastream);
Object o = oistream.readObject();
// CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"byteBufferToObject: converted class type "+o.getClass().getName() );
return(o);
}
private static byte[] objectToByteArray(Object o) throws SerializeException
{
ObjectOutputStream ostream;
ByteArrayOutputStream bstream;
byte[] barray;
try {
bstream = new ByteArrayOutputStream();
ostream = new ObjectOutputStream(bstream );
ostream.writeObject(o);
ostream.close();
barray = bstream.toByteArray();
// System.arraycopy(barray, 0, buf, offset, barray.length);
} catch (Exception e) {
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"objectToByteArray: exception serializing object "+o.getClass().getName()+" "+ e.getMessage() );
throw new SerializeException();
}
return(barray);
}
private static int objectToByteArray(Object o, byte[] buf, int offset) throws SerializeException
{
ObjectOutputStream ostream;
ByteArrayOutputStream bstream;
byte[] barray;
try {
bstream = new ByteArrayOutputStream();
ostream = new ObjectOutputStream(bstream );
ostream.writeObject(o);
ostream.close();
barray = bstream.toByteArray();
System.arraycopy(barray, 0, buf, offset, barray.length);
} catch (Exception e) {
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"objectToByteArray: exception serializing object "+o.getClass().getName()+" "+ e.getMessage() );
throw new SerializeException();
}
return(barray.length);
}
public static Object byteArrayToObject(byte[] b, int offset, int length) throws SerializeException
{
try {
ByteArrayInputStream bastream = new ByteArrayInputStream(b, offset, length);
ObjectInputStream oistream = new ObjectInputStream(bastream);
Object o = oistream.readObject();
// CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"byteBufferToObject: converted class type "+o.getClass().getName() );
return(o);
} catch (Exception e) {
// this could be a simple application problem, or something far more pernicious
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"byteBufferToObject: exception deserializing object "+e.getMessage()+" len "+length);
// e.printStackTrace(System.err);
dump_buf("serializer error ", b, offset, length);
if (e.getCause() != null)
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"byteBufferToObject: exception deserializing object - cause "+e.getCause().toString() );
throw new SerializeException();
}
}
// Java's internal UTF8 conversion is very, very slow.
// This is, rather amazingly, 8x faster than the to-string method
// returns the number of bytes this transalated into
public static int stringToUtf8(String s, byte[] buf, int offset)
{
int l = s.length();
int start_offset = offset;
for (int i=0;i<l;i++) {
char c = s.charAt(i);
if (c < 0x7f) {
buf[offset] = (byte) c;
offset++;
}
else if (c < 0x07FF) {
buf[offset] = (byte) (0xC0 | (c >> 6));
buf[offset+1] = (byte) (0x80 | (c & 0x5f));
offset += 2;
}
else {
//Encountered a different encoding other than 2-byte UTF8. Let java handle it.
try {
byte[] value = s.getBytes("UTF8");
System.arraycopy(value, 0, buf, start_offset, value.length);
return value.length;
}
catch (UnsupportedEncodingException e) {
//This should never happen as we are using standard encoding
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"Encountered illegally encoded string");
return 0;
}
}
}
return(offset - start_offset);
}
public static String utf8ToString(byte[] buf, int offset, int length) throws SerializeException
{
StringBuilder sb = new StringBuilder(length);
int limit = offset+length;
int origoffset = offset;
while (offset < limit ) {
if ((buf[offset] & 0x80) == 0) { // 1 byte
char c = (char) buf[offset];
sb.append(c);
offset++;
}
else if ((buf[offset] & 0xE0) == 0xC0) { // 2 bytes
char c = (char) (((buf[offset] & 0x1f) << 6) | (buf[offset+1] & 0x3f));
sb.append(c);
offset += 2;
}
else {
//Encountered an UTF encoding which uses more than 2bytes.
//Use a native function to do the conversion.
try {
String s = new String(buf, origoffset, length, "UTF8");
return s;
}
catch(Exception e) {
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"Encountered illegally encoded string");
throw new SerializeException();
}
}
}
return( sb.toString() );
}
public static Object bytesToBigInteger(byte[] buf, int offset, int len) {
boolean is_neg;
if ((buf[offset] & 0x80) != 0) {
is_neg = true;
buf[offset] &= 0x7f;
} else {
is_neg = false;
}
byte[] ba = new byte[len];
System.arraycopy(buf, offset, ba, 0, len);
BigInteger a = new BigInteger(ba);
if (is_neg) a = a.negate();
return(a);
}
public static Object bytesToInteger(byte[] buf, int offset, int len) {
if (len == 0) return(new Integer(0));
if (len > 8) return(bytesToBigInteger(buf, offset, len));
// This will work for negative integers too which
// will be represented in 2's complemenet representation
long a=0;
for (int i=0; i<len; i++) {
a <<= 8;
a |= buf[offset+i] & 0xFF;
}
if (a <= Integer.MAX_VALUE && a >= Integer.MIN_VALUE)
return(new Integer((int) a));
return(new Long(a));
}
public static void dump_buf(String info, byte[] buf, int limit) {
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"dump buffer: "+info+" size "+buf.length+" limit "+limit);
if (limit == 0) limit = buf.length;
for (int i=0;i<limit;i++) {
System.err.format(" %02x", buf[i]);
if (i % 16 == 7)
System.err.print(":");
if (i % 16 == 15)
System.err.println();
}
System.err.println();
}
// Todo: pass in the StreamWriter instead of using System.err
public static void dump_buf(String info, byte[] buf, int offset, int len) {
CitrusleafClient.ClLog(CitrusleafClient.ClLogLevel.INFO,"dump buffer: "+info+" bufsize "+buf.length+" offset "+offset+" len "+len);
if (len == 0) len = buf.length - offset; // do to end
for (int i=offset,pos=0;i<offset+len;i++,pos++) {
System.err.format(" %02x", buf[i]);
if (pos % 16 == 7)
System.err.print(":");
if (pos % 16 == 15)
System.err.println();
}
System.err.println();
}
}