org.freeswitch.esl.client.transport.message.EslFrameDecoder.java Source code

Java tutorial

Introduction

Here is the source code for org.freeswitch.esl.client.transport.message.EslFrameDecoder.java

Source

/*
 * Copyright 2010 david varnes.
 *
 * Licensed under the Apache License, version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at:
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.freeswitch.esl.client.transport.message;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ReplayingDecoder;
import io.netty.handler.codec.TooLongFrameException;
import org.freeswitch.esl.client.transport.HeaderParser;
import org.freeswitch.esl.client.transport.message.EslHeaders.Name;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;

/**
 * Decoder used by the IO processing pipeline. Client consumers should never need to use
 * this class.
 * <p/>
 * Follows the following decode algorithm (from FreeSWITCH wiki)
 * <pre>
 *    Look for \n\n in your receive buffer
 *
 *    Examine data for existence of Content-Length
 *
 *    If NOT present, process event and remove from receive buffer
 *
 *    IF present, Shift buffer to remove 'header'
 *    Evaluate content-length value
 *
 *    Loop until receive buffer size is >= Content-length
 *    Extract content-length bytes from buffer and process
 * </pre>
 */
public class EslFrameDecoder extends ReplayingDecoder<EslFrameDecoder.State> {
    /**
     * Line feed character
     */
    static final byte LF = 10;

    protected enum State {
        READ_HEADER, READ_BODY,
    }

    private final Logger log = LoggerFactory.getLogger(this.getClass());
    private final int maxHeaderSize;
    private EslMessage currentMessage;
    private boolean treatUnknownHeadersAsBody = false;

    public EslFrameDecoder(int maxHeaderSize) {
        super(State.READ_HEADER);
        if (maxHeaderSize <= 0) {
            throw new IllegalArgumentException("maxHeaderSize must be a positive integer: " + maxHeaderSize);
        }
        this.maxHeaderSize = maxHeaderSize;
    }

    public EslFrameDecoder(int maxHeaderSize, boolean treatUnknownHeadersAsBody) {
        this(maxHeaderSize);
        this.treatUnknownHeadersAsBody = treatUnknownHeadersAsBody;
    }

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception {
        State state = state();

        log.trace("decode() : state [{}]", state);
        switch (state) {
        case READ_HEADER:
            if (currentMessage == null) {
                currentMessage = new EslMessage();
            }
            /*
                    *  read '\n' terminated lines until reach a single '\n'
                    */
            boolean reachedDoubleLF = false;
            while (!reachedDoubleLF) {
                // this will read or fail
                String headerLine = readToLineFeedOrFail(buffer, maxHeaderSize);
                log.debug("read header line [{}]", headerLine);
                if (!headerLine.isEmpty()) {
                    // split the header line
                    String[] headerParts = HeaderParser.splitHeader(headerLine);
                    Name headerName = Name.fromLiteral(headerParts[0]);
                    if (headerName == null) {
                        if (treatUnknownHeadersAsBody) {
                            // cache this 'header' as a body line <-- useful for Outbound client mode
                            currentMessage.addBodyLine(headerLine);
                        } else {
                            throw new IllegalStateException("Unhandled ESL header [" + headerParts[0] + ']');
                        }
                    }
                    currentMessage.addHeader(headerName, headerParts[1]);
                } else {
                    reachedDoubleLF = true;
                }
                // do not read in this line again
                checkpoint();
            }
            // have read all headers - check for content-length
            if (currentMessage.hasContentLength()) {
                checkpoint(State.READ_BODY);
                log.debug("have content-length, decoding body ..");
                //  force the next section

                break;
            } else {
                // end of message
                checkpoint(State.READ_HEADER);
                // send message upstream
                EslMessage decodedMessage = currentMessage;
                currentMessage = null;

                out.add(decodedMessage);
                break;
            }

        case READ_BODY:
            /*
                    *   read the content-length specified
                    */
            int contentLength = currentMessage.getContentLength();
            ByteBuf bodyBytes = buffer.readBytes(contentLength);
            log.debug("read [{}] body bytes", bodyBytes.writerIndex());
            // most bodies are line based, so split on LF
            while (bodyBytes.isReadable()) {
                String bodyLine = readLine(bodyBytes, contentLength);
                log.debug("read body line [{}]", bodyLine);
                currentMessage.addBodyLine(bodyLine);
            }

            // end of message
            checkpoint(State.READ_HEADER);
            // send message upstream
            EslMessage decodedMessage = currentMessage;
            currentMessage = null;

            out.add(decodedMessage);
            break;

        default:
            throw new Error("Illegal state: [" + state + ']');
        }
    }

    private String readToLineFeedOrFail(ByteBuf buffer, int maxLineLegth) throws TooLongFrameException {
        StringBuilder sb = new StringBuilder(64);
        while (true) {
            // this read might fail
            byte nextByte = buffer.readByte();
            if (nextByte == LF) {
                return sb.toString();
            } else {
                // Abort decoding if the decoded line is too large.
                if (sb.length() >= maxLineLegth) {
                    throw new TooLongFrameException("ESL header line is longer than " + maxLineLegth + " bytes.");
                }
                sb.append((char) nextByte);
            }
        }
    }

    private String readLine(ByteBuf buffer, int maxLineLength) throws TooLongFrameException {
        StringBuilder sb = new StringBuilder(64);
        while (buffer.isReadable()) {
            // this read should always succeed
            byte nextByte = buffer.readByte();
            if (nextByte == LF) {
                return sb.toString();
            } else {
                // Abort decoding if the decoded line is too large.
                if (sb.length() >= maxLineLength) {
                    throw new TooLongFrameException("ESL message line is longer than " + maxLineLength + " bytes.");
                }
                sb.append((char) nextByte);
            }
        }

        return sb.toString();
    }
}