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

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

////////////////////////////////////////////////////////////////////////////
//
// 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 java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.ByteBuffer;

import com.crankuptheamps.client.exception.StreamException;

    /**
    * Implements the ProtocolParser interface. Parses a byte stream
    * using XML protocol. 
    */


public class XMLProtocolParser implements ProtocolParser
{
    private static final Charset LATIN1      = StandardCharsets.ISO_8859_1;
    private static byte[] HEADER             = null;
    private static byte[] BODY               = null;
    private static byte[] BODY_TERMINAL      = null;
    private static byte[] BODY_EMPTY         = null;

    static
    {
        HEADER          = "".getBytes(LATIN1);
        BODY            = "".getBytes(LATIN1);
        BODY_TERMINAL   = "".getBytes(LATIN1);
        BODY_EMPTY      = "".getBytes(LATIN1);
    }

    private XMLMessage         message        = null;
    private ByteBuffer         buffer         = null;
    private int                remainingBytes = 0;

    private enum StreamState
    {
        start, in_sow, end
    }
    private StreamState state;

    public XMLProtocolParser(XMLProtocol 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);
        }
    }

    private static enum HeaderField
    {
        AckTyp,BkMrk,BtchSz,ClntName,Cmd,CmdId,DlvMd,
        Drblty,Expn,Fltr,GrcPrd,GrpSqNum,Hrtbt,LeasePeriod,Matches,
        MsgLn,Opts,PW,QId,QryIntvl,Reason,RecordsUpdated,RecordsInserted,
        RecordsDeleted,RecordsReturned,Seq,SubIds,SowKey,SowKeys,Status,SubId,
        Timestamp,TmIntvl,Tpc,TopicMatches,TopN,UsrId,Version,CrlId,
        UNKNOWN
    }

    final private HeaderField extractHeaderField()
    {
        if(remainingBytes < 5 || getByte() != '<')
        {
            // Not enough room for the shortest tag ''
            return HeaderField.UNKNOWN;
        }

        // Find the end of the tag, so that we can optimize the field lookup
        int length   = 0;
        int start    = buffer.position();
        while(remainingBytes > 0 && getByte() != '>')
        {
            ++length;
        }

        switch(peekByte(start))
        {
        case 'A':
            if(length == 6)
            {
                return HeaderField.AckTyp;
            }
            else return HeaderField.UNKNOWN;
        case 'B':
            if(length == 5) return HeaderField.BkMrk;
            if(length != 6) return HeaderField.UNKNOWN;
            if(peekByte(start+4) == 'S')
            {
                return HeaderField.BtchSz;
            }
            return HeaderField.UNKNOWN;
        case 'C':
            switch(length)
            {
            case 3:  // Cmd
                return HeaderField.Cmd;
            case 5:  // CmdId
                switch(peekByte(start+1))
                {
                case 'm':return HeaderField.CmdId;
                case 'r':return HeaderField.CrlId;
                }
                return HeaderField.UNKNOWN;
            case 8:  // ClntName
                return HeaderField.ClntName;
            default:
                return HeaderField.UNKNOWN;
            }
        case 'D':
            switch(length)
            {
            case 5:  // DlvMd
                return HeaderField.DlvMd;
            case 6:  // Drblty
                return HeaderField.Drblty;
            default:
                return HeaderField.UNKNOWN;
            }
        case 'E':
            if(length == 4)
            {
                advance(length);
                return HeaderField.Expn;
            }
            else return HeaderField.UNKNOWN;
        case 'F':
            if(length == 4)
            {
                return HeaderField.Fltr;
            }
            else return HeaderField.UNKNOWN;
        case 'G':
            switch(length)
            {
            case 6:  // GrcPrd
                return HeaderField.GrcPrd;
            case 8:  // GrpSqNum
                return HeaderField.GrpSqNum;
            default:
                return HeaderField.UNKNOWN;
            }
        case 'H':
            if(length == 4)
            {
                return HeaderField.Hrtbt;
            }
            else return HeaderField.UNKNOWN;
        case 'L':
            switch(length)
            {
                case 11:
                    return HeaderField.LeasePeriod;
                default:
                    return HeaderField.UNKNOWN;
            }
        case 'M':
            switch(length)
            {
            case 5:  // MsgLn
                return HeaderField.MsgLn;
            case 7:  // Matches
                return HeaderField.Matches;
            default:
                return HeaderField.UNKNOWN;
            }
        case 'O':
            switch(length)
            {
            case 4:  // Opts
                return HeaderField.Opts;
            default:
                return HeaderField.UNKNOWN;
            }
        case 'P':
            switch(length)
            {
            case 2:  // PW
                return HeaderField.PW;
            default:
                return HeaderField.UNKNOWN;
            }
        case 'Q':
            switch(length)
            {
            case 3:  // QId
                return HeaderField.QId;
            default:
                return HeaderField.UNKNOWN;
            }
        case 'R':
            switch(length)
            {
            case 6:  // Reason
                return HeaderField.Reason;
            case 15:
                switch(peekByte(start+7))
                {
                case 'R': // RecordsReturned
                    return HeaderField.RecordsReturned;
                case 'I': // RecordsInserted
                    return HeaderField.RecordsInserted;
                default:
                    return HeaderField.UNKNOWN;
                }
            case 14:
                switch(peekByte(start+7))
                {
                case 'D': //  RecordsDeleted
                    return HeaderField.RecordsDeleted;
                case 'U': //  RecordsUpdated
                    return HeaderField.RecordsUpdated;
                default:
                    return HeaderField.UNKNOWN;
                }
            default:
                return HeaderField.UNKNOWN;
            }
        case 'S':
            switch(length)
            {
            case 3:  // Seq
                return HeaderField.Seq;
            case 5:  // SubId
                return HeaderField.SubId;
            case 6:  // SubIds,SndOOF,SowKey,Status
                switch(peekByte(start+1))
                {
                case 'u': // SubIds
                    return HeaderField.SubIds;
                case 'o': // SowKey
                    return HeaderField.SowKey;
                case 't': // Status
                    return HeaderField.Status;
                default:
                    return HeaderField.UNKNOWN;
                }
            case 7:  // SowKeys
                return HeaderField.SowKeys;
            default:
                return HeaderField.UNKNOWN;
            }
        case 'T':
            switch(length)
            {
            case 3:  // Tpc
                return HeaderField.Tpc;
            case 4:  // TopN
                return HeaderField.TopN;
            case 5:  // TxmTm
                return HeaderField.Timestamp;
            case 7:  // TmIntvl
                return HeaderField.TmIntvl;
            default:
                return HeaderField.UNKNOWN;
            }
        case 'U':
            if(length != 5) return HeaderField.UNKNOWN;
            switch(peekByte(start+2))
            {
            case 'r':  // UsrId
                return HeaderField.UsrId;
            default:
                return HeaderField.UNKNOWN;
            }
        case 'V':
            if(length != 7) return HeaderField.UNKNOWN;
            return HeaderField.Version;
        default:
            return HeaderField.UNKNOWN;
        }
    }

    final private char getUnescapedChar(int startPos)
    {
        switch(peekByte(startPos+1))
        {
        case 'a':
            switch(peekByte(startPos+2))
            {
                case 'm':                 //amp;
                    return '&';
                case 'p':                //apos;
                    return '\'';
            }
        case 'l':
            return '<';
        case 'g':
            return '>';
        case 'q':
            return '\"';
        default:
            return 0x00;
        }
    }

    final private int processEscapeField()
    {
        int b = buffer.position();
        int valueLength = 0;
        while(peekByte(buffer.position()) != '<')
        {
            if(peekByte(buffer.position()) == '&')
            {
                char unescapedChar = getUnescapedChar(buffer.position());
                this.buffer.put(b++, (byte)unescapedChar);
                advancePast((byte)';');
            }
            else
                this.buffer.put(b++, getByte());

            ++valueLength;
        }
        return valueLength;
    }

    final private void extractFieldValues(XMLMessage message)
    {
        HeaderField field = extractHeaderField();
        while(field != HeaderField.UNKNOWN)
        {
            boolean needsEscape = (field == HeaderField.Tpc || field == HeaderField.UsrId || 
                                   field == HeaderField.PW  || field == HeaderField.ClntName ||
                                   field == HeaderField.CrlId || field == HeaderField.SubId ||
                                   field == HeaderField.SubIds); 
            // Have a header field, let's extract the value
            int valueStart  = buffer.position();
            int valueLength = 0;
            if(needsEscape)
                valueLength = processEscapeField();
            else
            {
                while(remainingBytes > 0 && getByte() != '<') 
                    ++valueLength;
            }
            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 Opts:
                message._Options.set(this.buffer.array(),valueStart,valueLength);
                break;
            case PW:
                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 Timestamp:
                message._Timestamp.set(this.buffer.array(),valueStart,valueLength);
                break;
//          case TmIntvl:         message._TimeoutInterval.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;
            }

            // Strip to end of this tag
            while(remainingBytes > 0 && getByte() != '>')
            {
                ;  // Just advancing past the tag
            }
            field = extractHeaderField();
        }
    }


    private static byte[] SOW_KEYID         = null;
    private static byte[] SOW_LEN           = null;
    private static byte[] SOW_TS            = null;
    private static byte[] SOW_X             = null;
    private static byte   DBL_QUOTE         = 0;
    private static byte   GREATER_THAN      = 0;

    static
    {
        SOW_KEYID          = " key=\"".getBytes(LATIN1);
        SOW_LEN            = " len=\"".getBytes(LATIN1);
        SOW_TS            = " ts=\"".getBytes(LATIN1);
        SOW_X            = " x=\"".getBytes(LATIN1);
        DBL_QUOTE          = "\"".getBytes(LATIN1)[0];
        GREATER_THAN       = ">".getBytes(LATIN1)[0];
    }

    final private boolean extractSOWHeaderFields(XMLMessage message)
    {
        int position = advancePast(SOW_KEYID);
        if(position < 0)
        {
            // No SowKey
            return false;
        }

        // Read Key
        if(advancePast(DBL_QUOTE) < 0)
        {
            // broken record
            return false;
        }
        message._SowKey.set(buffer.array(),position,buffer.position()-position-1);

        // We've had the length in the past, let's assume we always have it
        position = advancePast(SOW_LEN);
        if(position < 0)
        {
            // No len, but had one in the past?
            return false;
        }
        // Read Key
        if(advancePast(DBL_QUOTE) < 0)
        {
            // broken record
            return false;
        }

        message._Length.set(buffer.array(),position,buffer.position()-position-1);

        position = buffer.position();
        if(peekByte(position+1)=='t' && peekByte(position+2)=='s')
        {
            position=advancePast(SOW_TS);
            advancePast(DBL_QUOTE);
            message._Timestamp.set(buffer.array(),position,buffer.position()-position-1);
        }
        position = buffer.position();
        if(peekByte(position+1)=='x' && peekByte(position+2)=='=')
        {
            position=advancePast(SOW_X);
            advancePast(DBL_QUOTE);
            message._CorrelationId.set(buffer.array(),position,buffer.position()-position-1);
        }
        // Advance to the start of the sow record body
        if(advancePast(GREATER_THAN) < 0)
        {
            return false;
        }
        // Body starts at buffer.position and the message length exists
        position = buffer.position();
        int len = message.getLength();
        message.setRawBufferLength(position + len - message.getRawBufferOffset());
        message._Data.set(buffer.array(),position,len);
        return true;
    }

    // Returns position after sequence or -1 if not found
    private int advancePast(byte[] t)
    {
        for(int position = buffer.position(); position < buffer.position()+remainingBytes-t.length; ++position)
        {
            boolean found = true;
            for(int i = 0; i < t.length; ++i)
            {
                if(peekByte(position+i) != t[i])
                {
                    found = false;
                    break;
                }
            }
            if(found)
            {
                position += t.length;
                remainingBytes -= position-buffer.position();
                buffer.position(position);
                return position;
            }
        }
        return -1;
    }
    private int advancePast(byte t)
    {
        int c = remainingBytes;
        while(c-- > 0)
        {
            if(getByte() == t)
            {
                remainingBytes = c;
                return buffer.position();
            }
        }
        return -1;
    }
    private final void advance(int bytes)
    {
        remainingBytes -= bytes;
        buffer.position(buffer.position()+bytes);
    }
    private int findBytes(byte[] t)
    {
        for(int position = buffer.position(); position < buffer.limit()-t.length; ++position)
        {
            boolean found = true;
            for(int i = 0; i < t.length; ++i)
            {
                if(peekByte(position+i) != t[i])
                {
                    found = false;
                    break;
                }
            }
            if(found) return position;
        }
        return -1;
    }
    private final byte getByte()
    {
        --remainingBytes;
        return buffer.get();
    }

    // Peek forward by offset bytes
    private final byte peekByte(int offset)
    {
        return buffer.get(offset);
    }

    private boolean read(XMLMessage m) throws StreamException
    {
        if(remainingBytes <= 0)
        {
            // No more messages in this stream
            return false;
        }
        int position;
        if(state == StreamState.start)
        {
            // First time through, let's setup the header
            m.setRawBufferOffset(buffer.position());
            position = advancePast(HEADER);
            if(position < 0)
            {
                throw new StreamException("stream corruption: couldn't locate XML SOAP header.");
            }
            extractFieldValues(m);

            // The first message in a SOW message sets all parts of the header
            if(m.getCommand() == Message.Command.SOW)
            {
                if(advancePast(BODY) < 0)
                {
                    throw new StreamException("stream corruption: couldn't locate XML SOAP body.");
                }
                if(!extractSOWHeaderFields(m))
                {
                    throw new StreamException("stream corruption: couldn't locate message segment within XML SOAP body.");
                }

                state = StreamState.in_sow;
            }
            else       // Not a SOW message, so it's just a standard message
            {
                int bodyStart;
                if(advancePast(BODY) < 0)
                {
                    //  not found, let's check for empty body
                    if(advancePast(BODY_EMPTY) >= 0)
                    {
                        bodyStart = buffer.position();
                        position = bodyStart;
                    }
                    else // let's assume a naked body
                    {
                        bodyStart = buffer.position();
                        position = bodyStart + remainingBytes;
                    }
                }
                else
                {
                    bodyStart = buffer.position();
                    position = findBytes(BODY_TERMINAL);
                    if(position < 0)
                    {
                        throw new StreamException("stream corruption: couldn't locate XML SOAP body end.");
                    }
                }

                m.setRawBufferLength(position-m.getRawBufferOffset());
                m._Data.set(this.buffer.array(),bodyStart,position-bodyStart);
                state = StreamState.end;
                advance(remainingBytes);
            }
        }
        else if(state == StreamState.in_sow)
        {
            if(!extractSOWHeaderFields(m))
            {
                state = StreamState.end;
                advance(remainingBytes);
                return false;
            }
        }
        else return false;

        return true;
    }

    /* 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