com.crankuptheamps.client.JSONProtocolParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of amps-client Show documentation
Show all versions of amps-client Show documentation
AMPS Java client by 60East Technologies, Inc.
////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2010-2020 60East Technologies Inc., All Rights Reserved.
//
// This computer software is owned by 60East Technologies Inc. and is
// protected by U.S. copyright laws and other laws and by international
// treaties. This computer software is furnished by 60East Technologies
// Inc. pursuant to a written license agreement and may be used, copied,
// transmitted, and stored only in accordance with the terms of such
// license agreement and with the inclusion of the above copyright notice.
// This computer software or any other copies thereof may not be provided
// or otherwise made available to any other person.
//
// U.S. Government Restricted Rights. This computer software: (a) was
// developed at private expense and is in all respects the proprietary
// information of 60East Technologies Inc.; (b) was not developed with
// government funds; (c) is a trade secret of 60East Technologies Inc.
// for all purposes of the Freedom of Information Act; and (d) is a
// commercial item and thus, pursuant to Section 12.212 of the Federal
// Acquisition Regulations (FAR) and DFAR Supplement Section 227.7202,
// Government's use, duplication or disclosure of the computer software
// is subject to the restrictions set forth by 60East Technologies Inc..
//
////////////////////////////////////////////////////////////////////////////
package com.crankuptheamps.client;
//import com.crankuptheamps.client.XMLProtocolParser.HeaderField;
import com.crankuptheamps.client.exception.StreamException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
/**
* Implements the ProtocolParser interface. Parses a byte stream using JSON Protocol (AMPS Protocol).
*/
public class JSONProtocolParser implements ProtocolParser {
private static final String LATIN1 = "ISO-8859-1";
static final byte OPEN_BRACE = '{';
static final byte CLOSE_BRACE = '}';
static final byte QUOTE = '"';
static final byte COLON = ':';
static final byte BACKSLASH = '\\';
private final JSONMessage message;
private ByteBuffer buffer = null;
private int remainingBytes = 0;
private enum StreamState {start, in_sow, end}
private StreamState state;
public JSONProtocolParser(JSONProtocol messageType)
{
this.message = messageType.allocateMessage();
}
public void process(ByteBuffer buffer,
int remainingBytes,
MessageHandler listener) throws StreamException
{
this.buffer = buffer;
this.remainingBytes = remainingBytes;
this.state = StreamState.start;
message.reset();
message.setBuffer(buffer.array());
while (read(message))
{
listener.invoke(message);
}
}
static enum HeaderField {
AckTyp,
BkMrk,
BtchSz,
ClntName,
Cmd,
CmdId,
DlvMd,
Drblty,
Expn,
Fltr,
GrcPrd,
GrpSqNum,
Hrtbt,
LeasePeriod,
Matches,
MsgLn,
Options,
Password,
QId,
Reason,
RecordsInserted,
RecordsUpdated,
RecordsDeleted,
RecordsReturned,
Seq,
SubIds,
SowKey,
SowKeys,
Status,
SubId,
Timestamp,
TmIntvl,
Tpc,
TopicMatches,
TopN,
UsrId,
Version,
CrlId,
UNKNOWN
}
// starts with buffer position on open quote of field name
// advances through field name, through close quote and colon
// leaves buffer position on index after colon
HeaderField extractHeaderField() throws StreamException
{
assertByte(QUOTE);
// "":x
if (remainingBytes < 4)
{
throw new StreamException("stream corruption: premature end of header");
}
getByte(); // eat open quote
final int name_start = buffer.position();
scan_or_throw();
assertByte(QUOTE);
final int name_length = buffer.position() - name_start;
if (remainingBytes < 2)
{
throw new StreamException("stream corruption: premature end of header");
}
getByte(); // eat close quote
assertByte(COLON);
getByte();
final byte[] pTag_ = buffer.array();
switch (name_length)
{
case 1:
switch (pTag_[name_start + 0])
{
case 'c':
return HeaderField.Cmd;
case 't':
return HeaderField.Tpc;
case 's':
return HeaderField.Seq;
case 'a':
return HeaderField.AckTyp;
case 'e':
return HeaderField.Expn;
case 'o':
return HeaderField.Options;
case 'l':
return HeaderField.MsgLn;
case 'k':
return HeaderField.SowKey;
case 'v':
return HeaderField.Version;
case 'x':
return HeaderField.CrlId;
}
break;
case 2:
switch (pTag_[name_start + 1])
{
case 'w':
return HeaderField.Password; // pw (same as auth_key)
case 's':
return (pTag_[name_start + 0] == 'b') ? HeaderField.BtchSz : HeaderField.Timestamp; // bs (same as batch_size) or ts
case 'm':
return HeaderField.BkMrk; // bm (same as bookmark)
case 'p':
return HeaderField.LeasePeriod;
}
break;
case 3:
switch (pTag_[name_start + 1])
{
case 'm':
return HeaderField.Cmd; // cmd
case 'i':
return HeaderField.CmdId; // cid
case 'c':
return HeaderField.AckTyp; // ack
case 'e':
return HeaderField.Seq; // seq
}
break;
case 4:
switch (pTag_[name_start + 0])
{
case 'o':
return HeaderField.Options; // opts
case 's':
return HeaderField.SubIds; // sids - same as sub_ids
case 'g':
return HeaderField.GrpSqNum; // gseq - same as group_seq_num
}
break;
case 5:
switch (pTag_[name_start + 3])
{
case 'i':
return HeaderField.Tpc; // topic
case '_':
return HeaderField.TopN; // top_n
}
break;
case 6:
switch (pTag_[name_start + 1])
{
case 'm':
return HeaderField.CmdId; // cmd_id
case 'i':
return HeaderField.Fltr; // filter
case 't':
return HeaderField.Status; // status
case 'e':
return HeaderField.Reason; // reason
case 'u':
return HeaderField.SubId; // sub_id
}
break;
case 7:
switch (pTag_[name_start + 0])
{
case 'm':
return HeaderField.Matches; // matches
case 'u':
return HeaderField.UsrId; // user_id
case 's':
return HeaderField.SubIds; // sub_ids
case 'v':
return HeaderField.Version; // version
}
break;
case 8:
switch (pTag_[name_start + 6])
{
case 'p':
return HeaderField.AckTyp; // ack_type
case 'e':
return HeaderField.Password; // auth_key
case 'r':
return HeaderField.BkMrk; // bookmark
case 'i':
return HeaderField.QId; // query_id
case 'y':
return HeaderField.SowKeys; // sow_keys
}
break;
case 9:
switch (pTag_[name_start + 0])
{
case 'h':
return HeaderField.Hrtbt; // heartbeat
}
break;
case 10:
switch (pTag_[name_start + 0])
{
case 'e':
return HeaderField.Expn; // expiration
case 'b':
return HeaderField.BtchSz; // batch_size
}
break;
case 11:
switch (pTag_[name_start + 0])
{
case 'c':
return HeaderField.ClntName; // client_name
}
break;
case 13:
switch (pTag_[name_start + 0])
{
case 't':
return HeaderField.TopicMatches; // topic_matches
case 'g':
return HeaderField.GrpSqNum; // group_seq_num
}
break;
case 15:
switch (pTag_[name_start + 8])
{
case 'd':
return HeaderField.RecordsDeleted; // records_deleted
case 'u':
return HeaderField.RecordsUpdated; // records_updated
}
break;
case 16:
switch (pTag_[name_start + 8])
{
case 'i':
return HeaderField.RecordsInserted; // records_inserted
case 'r':
return HeaderField.RecordsReturned; // records_returned
}
break;
}
return HeaderField.UNKNOWN;
}
// starts with buffer position at value
// advances through buffer to the position following the value
// returns the index where the value ended
int findValueEnd(boolean fieldNeedsUnescape) throws StreamException
{
int end_index;
switch (peekByte())
{
case '"':
getByte(); //consume the quote
if(fieldNeedsUnescape)
{
end_index = unescapeField();
}
else
{
scan_or_throw();
end_index = buffer.position();
}
assertByte(QUOTE);
if (remainingBytes <= 0)
{
throw new StreamException("stream corruption: premature end of header");
}
getByte(); // eat quote
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '-':
while (remainingBytes > 0 && peekByte() >= '0' && peekByte() <= '9')
{
getByte();
}
end_index = buffer.position();
break;
case 't':
// true
if (remainingBytes < 4)
{
throw new StreamException("stream corruption: premature end of header");
}
getByte();
assertByte((byte) 'r');
getByte();
assertByte((byte) 'u');
getByte();
assertByte((byte) 'e');
getByte();
end_index = buffer.position();
break;
case 'f':
// false
if (remainingBytes < 5)
{
throw new StreamException("stream corruption: premature end of header");
}
getByte();
assertByte((byte) 'a');
getByte();
assertByte((byte) 'l');
getByte();
assertByte((byte) 's');
getByte();
assertByte((byte) 'e');
getByte();
end_index = buffer.position();
break;
case 'n':
// null
if (remainingBytes < 4)
{
throw new StreamException("stream corruption: premature end of header");
}
getByte();
assertByte((byte) 'u');
getByte();
assertByte((byte) 'l');
getByte();
assertByte((byte) 'l');
getByte();
end_index = buffer.position();
break;
default:
throw new StreamException("stream corruption: premature end of header");
}
return end_index;
}
void extractFieldValue(JSONMessage message) throws StreamException
{
HeaderField field = extractHeaderField();
boolean needsUnEscape = (field == HeaderField.Tpc || field == HeaderField.UsrId ||
field == HeaderField.Password || field == HeaderField.ClntName ||
field == HeaderField.SubId || field == HeaderField.SubIds);
final int valueStart = peekByte() == QUOTE ? buffer.position() + 1 : buffer.position();
final int valueLength = findValueEnd(needsUnEscape) - valueStart;
switch (field)
{
case AckTyp:
message._AckType.set(this.buffer.array(), valueStart, valueLength);
break;
case BtchSz:
message._BatchSize.set(this.buffer.array(), valueStart, valueLength);
break;
case BkMrk:
message._Bookmark.set(this.buffer.array(), valueStart, valueLength);
break;
case ClntName:
message._ClientName.set(this.buffer.array(), valueStart, valueLength);
break;
case Cmd:
message._Command.set(this.buffer.array(), valueStart, valueLength);
break;
case CmdId:
message._CommandId.set(this.buffer.array(), valueStart, valueLength);
break;
case CrlId:
message._CorrelationId.set(this.buffer.array(), valueStart, valueLength);
break;
case Expn:
message._Expiration.set(this.buffer.array(), valueStart, valueLength);
break;
case Fltr:
message._Filter.set(this.buffer.array(), valueStart, valueLength);
break;
case GrpSqNum:
message._GroupSeqNo.set(this.buffer.array(), valueStart, valueLength);
break;
// case Hrtbt: message._Heartbeat.set(this.buffer.array(),valueStart,valueLength); break;
case LeasePeriod:
message._LeasePeriod.set(this.buffer.array(),valueStart,valueLength);
break;
case Matches:
message._Matches.set(this.buffer.array(), valueStart, valueLength);
break;
case MsgLn:
message._Length.set(this.buffer.array(), valueStart, valueLength);
break;
case Options:
message._Options.set(this.buffer.array(), valueStart, valueLength);
break;
case Password:
message._Password.set(this.buffer.array(), valueStart, valueLength);
break;
case QId:
message._QueryId.set(this.buffer.array(), valueStart, valueLength);
break;
case Reason:
message._Reason.set(this.buffer.array(), valueStart, valueLength);
break;
case RecordsInserted:
message._RecordsInserted.set(this.buffer.array(), valueStart, valueLength);
break;
case RecordsUpdated:
message._RecordsUpdated.set(this.buffer.array(), valueStart, valueLength);
break;
case RecordsReturned:
message._RecordsReturned.set(this.buffer.array(), valueStart, valueLength);
break;
case RecordsDeleted:
message._RecordsDeleted.set(this.buffer.array(), valueStart, valueLength);
break;
case Seq:
message._Sequence.set(this.buffer.array(), valueStart, valueLength);
break;
case SubIds:
message._SubIds.set(this.buffer.array(), valueStart, valueLength);
break;
case SowKey:
message._SowKey.set(this.buffer.array(), valueStart, valueLength);
break;
case SowKeys:
message._SowKeys.set(this.buffer.array(), valueStart, valueLength);
break;
case Status:
message._Status.set(this.buffer.array(), valueStart, valueLength);
break;
case SubId:
message._SubId.set(this.buffer.array(), valueStart, valueLength);
break;
// case TmIntvl: message._TimeoutInterval.set(this.buffer.array(),valueStart,valueLength); break;
case Timestamp:
message._Timestamp.set(this.buffer.array(), valueStart, valueLength);
break;
case Tpc:
message._Topic.set(this.buffer.array(), valueStart, valueLength);
break;
case TopicMatches:
message._TopicMatches.set(this.buffer.array(), valueStart, valueLength);
break;
case TopN:
message._TopN.set(this.buffer.array(), valueStart, valueLength);
break;
case UsrId:
message._UserId.set(this.buffer.array(), valueStart, valueLength);
break;
case Version:
message._Version.set(this.buffer.array(), valueStart, valueLength);
break;
default:
break;
}
while (remainingBytes > 0 && peekByte() != QUOTE && peekByte() != CLOSE_BRACE)
{
getByte();
}
}
private boolean read(JSONMessage m) throws StreamException
{
if (remainingBytes <= 0)
{
// No more messages in this stream
return false;
}
if (state != StreamState.in_sow) {
m.setRawBufferOffset(buffer.position());
}
scan();
if (remainingBytes <= 0)
{
return false;
}
assertByte(OPEN_BRACE);
getByte();
while (remainingBytes > 0 && peekByte() != CLOSE_BRACE)
{
extractFieldValue(m);
}
assertByte(CLOSE_BRACE);
getByte();
int messageLength;
int command = m.getCommand();
if (state == StreamState.start && command == Message.Command.SOW)
{
scan();
if (remainingBytes <= 0)
{
return false;
}
assertByte(OPEN_BRACE);
getByte();
while (remainingBytes > 0 && peekByte() != CLOSE_BRACE)
{
extractFieldValue(m);
}
assertByte(CLOSE_BRACE);
getByte();
state = StreamState.in_sow;
messageLength = m._Length.getValue();
} else if (state == StreamState.in_sow)
{
messageLength = m._Length.getValue();
} else
{
messageLength = remainingBytes;
}
// Now, we have to deal with the data
m._Data.set(this.buffer.array(), buffer.position(), messageLength);
remainingBytes -= messageLength;
buffer.position(buffer.position() + messageLength);
m.setRawBufferLength(buffer.position() - m.getRawBufferOffset());
return true;
}
private final void assertByte(byte b) throws StreamException
{
if (peekByte() != b)
{
throw new StreamException("stream corruption: expected '" + (char) b + "'");
}
}
private boolean in_quote = false;
final byte getByte()
{
--remainingBytes;
byte b = buffer.get();
if (b == QUOTE)
{
in_quote = !in_quote;
}
return b;
}
final byte peekByte()
{
return buffer.get(buffer.position());
}
final private char getUnescapedChar()
{
switch(peekByte())
{
case '\"':
return '\"';
case '\\':
return '\\';
case '/':
return '/';
case 'b':
return '\b';
case 'f':
return '\f';
case 'n':
return '\n';
case 'r':
return '\r';
case 't':
return '\t';
default:
return 0x00;
}
}
int unescapeField()
{
int b = buffer.position();
while (remainingBytes > 0 && peekByte() != QUOTE)
{
if(peekByte() == BACKSLASH)
{
getByte();
char unescapedChar = getUnescapedChar();
//consume unescaped char without toggling in_quote, results in buffer corruption
--remainingBytes;
buffer.get();
this.buffer.put(b++, (byte)unescapedChar);
}
else
{
this.buffer.put(b++, getByte());
}
}
return b;
}
boolean scan()
{
// if we're in a quoted-string ignore braces and colons
while (remainingBytes > 0 &&
(
(in_quote
&& peekByte() != QUOTE) ||
(!in_quote
&& peekByte() != QUOTE
&& peekByte() != OPEN_BRACE
&& peekByte() != COLON
&& peekByte() != CLOSE_BRACE)
))
{
getByte();
}
return remainingBytes > 0;
}
void scan_or_throw() throws StreamException
{
if (!scan())
{
throw new StreamException("stream corruption: premature end of header");
}
}
/* Comment since not used.
private void dumpBuffer(String prefix)
{
System.err.print(prefix);
for(int j = buffer.position(); j < buffer.limit(); ++j)
{
try
{
System.err.print(new String(buffer.array(),j,1,LATIN1));
}
catch(Exception e)
{
System.err.print("{error}");
}
}
System.err.println();
}
*/
}