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

com.crankuptheamps.client.JSONProtocolParser Maven / Gradle / Ivy

////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2010-2022 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();
    }
    */
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy