001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.activemq.transport.stomp;
018
019import java.io.ByteArrayInputStream;
020import java.util.Arrays;
021import java.util.Collections;
022import java.util.HashMap;
023import java.util.HashSet;
024import java.util.Map;
025
026import org.apache.activemq.transport.tcp.TcpTransport;
027import org.apache.activemq.util.ByteArrayOutputStream;
028import org.apache.activemq.util.DataByteArrayInputStream;
029
030public class StompCodec {
031
032    final static byte[] crlfcrlf = new byte[]{'\r','\n','\r','\n'};
033    TcpTransport transport;
034    StompWireFormat wireFormat;
035
036    ByteArrayOutputStream currentCommand = new ByteArrayOutputStream();
037    boolean processedHeaders = false;
038    String action;
039    HashMap<String, String> headers;
040    int contentLength = -1;
041    int readLength = 0;
042    int previousByte = -1;
043    boolean awaitingCommandStart = true;
044    String version = Stomp.DEFAULT_VERSION;
045
046    public StompCodec(TcpTransport transport) {
047        this.transport = transport;
048        this.wireFormat = (StompWireFormat) transport.getWireFormat();
049    }
050
051    public void parse(ByteArrayInputStream input, int readSize) throws Exception {
052       int i = 0;
053       int b;
054       while(i++ < readSize) {
055           b = input.read();
056           // skip repeating nulls
057           if (!processedHeaders && previousByte == 0 && b == 0) {
058               continue;
059           }
060
061           if (!processedHeaders) {
062
063               // skip heart beat commands.
064               if (awaitingCommandStart && b == '\n') {
065                   continue;
066               } else {
067                   awaitingCommandStart = false;   // non-newline indicates next frame.
068               }
069
070               currentCommand.write(b);
071               // end of headers section, parse action and header
072               if (b == '\n' && (previousByte == '\n' || currentCommand.endsWith(crlfcrlf))) {
073                   DataByteArrayInputStream data = new DataByteArrayInputStream(currentCommand.toByteArray());
074                   action = wireFormat.parseAction(data);
075                   headers = wireFormat.parseHeaders(data);
076                   try {
077                       String contentLengthHeader = headers.get(Stomp.Headers.CONTENT_LENGTH);
078                       if ((action.equals(Stomp.Commands.SEND) || action.equals(Stomp.Responses.MESSAGE)) && contentLengthHeader != null) {
079                           contentLength = wireFormat.parseContentLength(contentLengthHeader);
080                       } else {
081                           contentLength = -1;
082                       }
083                   } catch (ProtocolException e) {
084                       transport.doConsume(new StompFrameError(e));
085                       return;
086                   }
087                   processedHeaders = true;
088                   currentCommand.reset();
089               }
090
091           } else {
092
093               if (contentLength == -1) {
094                   // end of command reached, unmarshal
095                   if (b == 0) {
096                       processCommand();
097                   } else {
098                       currentCommand.write(b);
099                       if (currentCommand.size() > wireFormat.getMaxDataLength()) {
100                           transport.doConsume(new StompFrameError(new ProtocolException("The maximum data length was exceeded", true)));
101                           return;
102                       }
103                   }
104               } else {
105                   // read desired content length
106                   if (readLength++ == contentLength) {
107                       processCommand();
108                       readLength = 0;
109                   } else {
110                       currentCommand.write(b);
111                   }
112               }
113           }
114
115           previousByte = b;
116       }
117    }
118
119    protected void processCommand() throws Exception {
120        StompFrame frame = new StompFrame(action, headers, currentCommand.toByteArray());
121        transport.doConsume(frame);
122        processedHeaders = false;
123        awaitingCommandStart = true;
124        currentCommand.reset();
125        contentLength = -1;
126    }
127
128    public static String detectVersion(Map<String, String> headers) throws ProtocolException {
129        String accepts = headers.get(Stomp.Headers.Connect.ACCEPT_VERSION);
130
131        if (accepts == null) {
132            accepts = Stomp.DEFAULT_VERSION;
133        }
134        HashSet<String> acceptsVersions = new HashSet<String>(Arrays.asList(accepts.trim().split(Stomp.COMMA)));
135        acceptsVersions.retainAll(Arrays.asList(Stomp.SUPPORTED_PROTOCOL_VERSIONS));
136        if (acceptsVersions.isEmpty()) {
137            throw new ProtocolException("Invalid Protocol version[" + accepts +"], supported versions are: " +
138                    Arrays.toString(Stomp.SUPPORTED_PROTOCOL_VERSIONS), true);
139        } else {
140            return Collections.max(acceptsVersions);
141        }
142    }
143}