/**
 *
 */
package io.pkts.packet.sip.impl;

import io.pkts.buffer.Buffer;
import io.pkts.buffer.Buffers;
import io.pkts.packet.sip.SipMessage;
import io.pkts.packet.sip.SipParseException;
import io.pkts.packet.sip.header.CSeqHeader;
import io.pkts.packet.sip.header.CallIdHeader;
import io.pkts.packet.sip.header.ContactHeader;
import io.pkts.packet.sip.header.ContentLengthHeader;
import io.pkts.packet.sip.header.ContentTypeHeader;
import io.pkts.packet.sip.header.ExpiresHeader;
import io.pkts.packet.sip.header.FromHeader;
import io.pkts.packet.sip.header.MaxForwardsHeader;
import io.pkts.packet.sip.header.RecordRouteHeader;
import io.pkts.packet.sip.header.RouteHeader;
import io.pkts.packet.sip.header.SipHeader;
import io.pkts.packet.sip.header.ToHeader;
import io.pkts.packet.sip.header.ViaHeader;
import io.pkts.packet.sip.header.impl.SipHeaderImpl;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;





/**
 * Basic sip parser that contains most (all?) of the different grammar rules for SIP as defined by
 * RFC 3261. View these various methods as building blocks for building up a complete SIP parser
 * (perhaps I should rename this class?).
 *
 * All of these functions work in the following way:
 * <ul>
 * <li><b>consumeXXXX</b> - will (in general) simply try and consume whatever it is supposed to
 * consume and the function will return true of false depending on whether is was able to consume
 * anything from the {@link Buffer}. The consume-functions will (if successful) move the reader
 * index of the {@link Buffer}. If unsuccessful, the reader index will be left untouched.</li>
 * <li><b>expectXXX</b> - will (in general) expect that the next thing is whatever it is supposed to
 * expect. These functions are really the same as the consumeXXXX ones but instead of returning true
 * or false the expect-functions will throw a {@link SipParseException} to indicate that things
 * didn't turn out as we were hoping for. Also, remember that the {@link SipParseException} contains
 * the error offset into the {@link Buffer} where things broke. As with the consume-functions, the
 * expect-functions will (if successful) move the reader index of the {@link Buffer}</li>
 * <li></li>
 * </ul>
 *
 * @author jonas@jonasborjesson.com
 */
public class SipParser {

    private static final String UNABLE_TO_READ_FROM_STREAM = "Unable to read from stream";

    /**
     * There are many situations where you are looking to frame something but
     * you cannot find the terminating condition. Since an attacker could easily
     * just have you read a never ending stream we need at some point give up
     * and abort.
     */
    public static final int MAX_LOOK_AHEAD = 1024;

    public static final Buffer INVITE = Buffers.wrap("INVITE");
    public static final Buffer ACK = Buffers.wrap("ACK");
    public static final Buffer CANCEL = Buffers.wrap("CANCEL");
    public static final Buffer BYE = Buffers.wrap("BYE");
    public static final Buffer SUBSCRIBE = Buffers.wrap("SUBSCRIBE");
    public static final Buffer NOTIFY = Buffers.wrap("NOTIFY");
    public static final Buffer PUBLISH = Buffers.wrap("PUBLISH");
    public static final Buffer INFO = Buffers.wrap("INFO");
    public static final Buffer OPTIONS = Buffers.wrap("OPTIONS");
    public static final Buffer REGISTER = Buffers.wrap("REGISTER");
    public static final Buffer PRACK = Buffers.wrap("PRACK");
    public static final Buffer REFER = Buffers.wrap("REFER");
    public static final Buffer MESSAGE = Buffers.wrap("MESSAGE");
    public static final Buffer UPDATE = Buffers.wrap("UPDATE");

    public static final Buffer TAG = Buffers.wrap("tag");

    public static final Buffer USER = Buffers.wrap("user");

    public static final Buffer TTL = Buffers.wrap("ttl");

    public static final Buffer MADDR = Buffers.wrap("maddr");

    public static final Buffer METHOD = Buffers.wrap("method");

    public static final Buffer TRANSPORT = Buffers.wrap("transport");

    public static final Buffer TRANSPORT_EQ = Buffers.wrap("transport=");

    public static final Buffer SIP2_0 = Buffers.wrap("SIP/2.0");

    public static final Buffer SIP2_0_SLASH = Buffers.wrap("SIP/2.0/");

    public static final Buffer SCHEME_SIP = Buffers.wrap("sip");

    public static final Buffer SCHEME_SIP_COLON = Buffers.wrap("sip:");

    public static final Buffer SCHEME_SIPS = Buffers.wrap("sips");

    public static final Buffer SCHEME_SIPS_COLON = Buffers.wrap("sips:");

    public static final Buffer SCHEME_TEL = Buffers.wrap("tel");

    public static final Buffer SCHEME_TEL_COLON = Buffers.wrap("tel:");

    public static final byte AT = '@';

    public static final byte COLON = ':';

    public static final byte SEMI = ';';

    public static final byte DOUBLE_QOUTE = '"';

    public static final byte CR = '\r';

    public static final byte LF = '\n';

    public static final byte SP = ' ';

    public static final byte HTAB = '\t';

    public static final byte DASH = '-';

    public static final byte PERIOD = '.';

    public static final byte COMMA = ',';

    public static final byte EXCLAMATIONPOINT = '!';

    public static final byte PERCENT = '%';

    public static final byte STAR = '*';

    public static final byte UNDERSCORE = '_';

    public static final byte QUESTIONMARK = '?';

    public static final byte PLUS = '+';

    public static final byte BACKTICK = '`';

    public static final byte TICK = '\'';

    public static final byte TILDE = '~';

    public static final byte EQ = '=';

    public static final byte SLASH = '/';

    public static final byte BACK_SLASH = '\\';

    /**
     * Left parenthesis
     */
    public static final byte LPAREN = '(';

    /**
     * Right parenthesis
     */
    public static final byte RPAREN = ')';

    /**
     * Right angle quote
     */
    public static final byte RAQUOT = '>';

    /**
     * Left angle quote
     */
    public static final byte LAQUOT = '<';

    /**
     * Left square bracket
     */
    public static final byte LSBRACKET = '[';

    /**
     * Right square bracket
     */
    public static final byte RSBRACKET = ']';

    /**
     * Double quotation mark
     */
    public static final byte DQUOT = '"';

    public static final Buffer UDP = Buffers.wrap("udp");

    public static final Buffer TCP = Buffers.wrap("tcp");

    public static final Buffer TLS = Buffers.wrap("tls");

    public static final Buffer SCTP = Buffers.wrap("sctp");

    public static final Buffer WS = Buffers.wrap("ws");

    public static final Buffer WSS = Buffers.wrap("wss");

    public static final Map<Buffer, Function<SipHeader, ? extends SipHeader>> framers = new HashMap<>();

    static {
        framers.put(CallIdHeader.NAME, header -> CallIdHeader.frame(header.getValue()));
        framers.put(CallIdHeader.COMPACT_NAME, header -> CallIdHeader.frameCompact(header.getValue()));

        framers.put(ContactHeader.NAME, header -> ContactHeader.frame(header.getValue()));
        framers.put(ContactHeader.COMPACT_NAME, header -> ContactHeader.frame(header.getValue()));

        framers.put(ContentTypeHeader.NAME, header -> ContentTypeHeader.frame(header.getValue()));
        framers.put(ContentTypeHeader.COMPACT_NAME, header -> ContentTypeHeader.frame(header.getValue()));

        framers.put(ContentLengthHeader.NAME, header -> ContentLengthHeader.frame(header.getValue()));
        framers.put(ContentLengthHeader.COMPACT_NAME, header -> ContentLengthHeader.frame(header.getValue()));

        framers.put(CSeqHeader.NAME, header -> CSeqHeader.frame(header.getValue()));

        framers.put(ExpiresHeader.NAME, header -> ExpiresHeader.frame(header.getValue()));

        framers.put(FromHeader.NAME, header -> FromHeader.frame(header.getValue()));
        framers.put(FromHeader.COMPACT_NAME, header -> FromHeader.frame(header.getValue()));

        framers.put(MaxForwardsHeader.NAME, header -> MaxForwardsHeader.frame(header.getValue()));

        framers.put(RecordRouteHeader.NAME, header -> RecordRouteHeader.frame(header.getValue()));

        framers.put(RouteHeader.NAME, header -> RouteHeader.frame(header.getValue()));

        framers.put(ToHeader.NAME, header -> ToHeader.frame(header.getValue()));
        framers.put(ToHeader.COMPACT_NAME, header -> ToHeader.frame(header.getValue()));

        framers.put(ViaHeader.NAME, header -> ViaHeader.frame(header.getValue()));
        framers.put(ViaHeader.COMPACT_NAME, header -> ViaHeader.frame(header.getValue()));
    }

    /**
     * For the given header name, return a function that will convert a generic header instance
     * into one with the correct subtype.
     */
    public static Function<SipHeader, ? extends SipHeader> getFramer(final Buffer b) {
        // For headers that have the expected capitalization, do a quick case-sensitive
        // search. If that fails do a slower case-insensitive search.
        final Function<SipHeader, ? extends SipHeader> framer = framers.get(b);
        if (framer != null) {
            return framer;
        }
        for (final Map.Entry<Buffer, Function<SipHeader, ? extends SipHeader>> entry : framers.entrySet()) {
            if (entry.getKey().equalsIgnoreCase(b)) {
                return entry.getValue();
            }
        }
        return null;
    }

    // ----------------------------------------------------------------------
    // ----------------------------------------------------------------------
    // -------- Expect methods expects something to be true and if not ------
    // -------- they will throw an exception of some sort -------------------
    // ----------------------------------------------------------------------
    // ----------------------------------------------------------------------

    /**
     * Expect that the next set of bytes is "SIP/2.0" and if not then we will
     * throw a {@link SipParseException}
     *
     * @param buffer
     */
    public static void expectSIP2_0(final Buffer buffer) throws SipParseException {
        expect(buffer, 'S');
        expect(buffer, 'I');
        expect(buffer, 'P');
        expect(buffer, '/');
        expect(buffer, '2');
        expect(buffer, '.');
        expect(buffer, '0');
    }

    /**
     *
     * @return
     */
    public static Buffer expectMethod(final Buffer buffer) {
        return null;
    }


    /**
     * Check whether the buffer is exactly three bytes long and has the bytes "UDP" in it.
     *
     * @param t
     * @return
     */
    public static boolean isUDP(final Buffer t) {
        try {
            return t.capacity() == 3 && t.getByte(0) == 'U' && t.getByte(1) == 'D' && t.getByte(2) == 'P';
        } catch (final IOException e) {
            return false;
        }
    }

    public static boolean isTCP(final Buffer t) {
        try {
            return t.capacity() == 3 && t.getByte(0) == 'T' && t.getByte(1) == 'C' && t.getByte(2) == 'P';
        } catch (final IOException e) {
            return false;
        }
    }

    public static boolean isTLS(final Buffer t) {
        try {
            return t.capacity() == 3 && t.getByte(0) == 'T' && t.getByte(1) == 'L' && t.getByte(2) == 'S';
        } catch (final IOException e) {
            return false;
        }
    }

    public static boolean isWS(final Buffer t) {
        try {
            return t.capacity() == 2 && t.getByte(0) == 'W' && t.getByte(1) == 'S';
        } catch (final IOException e) {
            return false;
        }
    }

    public static boolean isWSS(final Buffer t) {
        try {
            return t.capacity() == 3 && t.getByte(0) == 'W' && t.getByte(1) == 'S' && t.getByte(2) == 'S';
        } catch (final IOException e) {
            return false;
        }
    }

    public static boolean isSCTP(final Buffer t) {
        try {
            return t.capacity() == 4 && t.getByte(0) == 'S' && t.getByte(1) == 'C' && t.getByte(2) == 'T'
                   && t.getByte(3) == 'P';
        } catch (final IOException e) {
            return false;
        }
    }

    /**
     * Check whether the buffer is exactly three bytes long and has the
     * bytes "udp" in it. Note, in SIP there is a different between transport
     * specified in a Via-header and a transport-param specified in a SIP URI.
     * One is upper case, one is lower case. Another really annoying thing
     * with SIP.
     *
     * @param t
     * @return
     */
    public static boolean isUDPLower(final Buffer t) {
        try {
            return t.capacity() == 3 && t.getByte(0) == 'u' && t.getByte(1) == 'd' && t.getByte(2) == 'p';
        } catch (final IOException e) {
            return false;
        }
    }

    public static boolean isTCPLower(final Buffer t) {
        try {
            return t.capacity() == 3 && t.getByte(0) == 't' && t.getByte(1) == 'c' && t.getByte(2) == 'p';
        } catch (final IOException e) {
            return false;
        }
    }

    public static boolean isTLSLower(final Buffer t) {
        try {
            return t.capacity() == 3 && t.getByte(0) == 't' && t.getByte(1) == 'l' && t.getByte(2) == 's';
        } catch (final IOException e) {
            return false;
        }
    }

    public static boolean isWSLower(final Buffer t) {
        try {
            return t.capacity() == 2 && t.getByte(0) == 'w' && t.getByte(1) == 's';
        } catch (final IOException e) {
            return false;
        }
    }

    public static boolean isWSSLower(final Buffer t) {
        try {
            return t.capacity() == 3 && t.getByte(0) == 'w' && t.getByte(1) == 's' && t.getByte(2) == 's';
        } catch (final IOException e) {
            return false;
        }
    }

    public static boolean isSCTPLower(final Buffer t) {
        try {
            return t.capacity() == 4 && t.getByte(0) == 's' && t.getByte(1) == 'c' && t.getByte(2) == 't'
                   && t.getByte(3) == 'p';
        } catch (final IOException e) {
            return false;
        }
    }

    public static void expectTel(final Buffer buffer) throws SipParseException, IOException {
        expect(buffer, 't');
        expect(buffer, 'e');
        expect(buffer, 'l');
        expect(buffer, SipParser.COLON);
    }

    public static boolean isSips(final Buffer buffer) throws SipParseException, IndexOutOfBoundsException, IOException {
        SipParser.expect(buffer, 's');
        SipParser.expect(buffer, 'i');
        SipParser.expect(buffer, 'p');
        final byte b = buffer.readByte();
        if (b == SipParser.COLON) {
            return false;
        }

        if (b != 's') {
            throw new SipParseException(buffer.getReaderIndex() - 1,
                                        "Expected 's' since the only schemes accepted are \"sip\" and \"sips\"");
        }

        SipParser.expect(buffer, SipParser.COLON);
        return true;
    }


    /**
     * Consumes all generic-params it can find. If there are no generic params
     * to be consumed it will return an empty list. The list of generic-params
     * are found on many SIP headers such as the To, From etc. In general, the
     * BNF looks like so:
     *
     *
     * <pre>
     *  *( SEMI generic-param )
     * </pre>
     *
     * E.g. in To:
     *
     * <pre>
     * To        =  ( "To" / "t" ) HCOLON ( name-addr / addr-spec ) *( SEMI to-param )
     * to-param  =  tag-param / generic-param
     * </pre>
     *
     * @param buffer
     * @return
     * @throws IOException
     * @throws IndexOutOfBoundsException
     */
    public static List<Buffer[]> consumeGenericParams(final Buffer buffer) throws IndexOutOfBoundsException,
                    IOException {
        final List<Buffer[]> params = new ArrayList<Buffer[]>();
        while (buffer.hasReadableBytes() && buffer.peekByte() == SEMI) {
            buffer.readByte(); // consume the SEMI
            params.add(consumeGenericParam(buffer));
        }
        return params;
    }

    /**
     * Consumes a generic param, which according to RFC 3261 section 25.1 is:
     *
     * <pre>
     * generic-param  =  token [ EQUAL gen-value ]
     * gen-value      =  token / host / quoted-string
     * </pre>
     *
     * The return value is two buffers returned as an array and if the second
     * element is null (so make sure you check it!) then there was no value for
     * this parameter.
     *
     * Also note that due to poor implementations out there, the following is
     * accepted as valid input:
     *
     * foo=
     *
     * I.e., foo is a flag parameter and as such there should not be an equal
     * sign following it but there are many servers out there that does this
     * anyway.
     *
     * @param buffer
     *            the buffer from which we will consume a generic-param
     * @return a buffer array of size two. The first element is the name of the
     *         parameter and the second element is the value of that parameter
     *         or null if the parameter was a flag parameter (didn't have a
     *         value)
     * @throws IOException
     * @throws IndexOutOfBoundsException
     */
    public static Buffer[] consumeGenericParam(final Buffer buffer) throws IndexOutOfBoundsException, IOException {
        final Buffer key = consumeToken(buffer);
        Buffer value = null;
        if (key == null) {
            return new Buffer[2];
        }
        if (consumeEQUAL(buffer) > 0) {
            // TODO: consume host and quoted string
            if (isNext(buffer, DOUBLE_QOUTE)) {
                value = consumeQuotedString(buffer);
            } else {
                value = consumeToken(buffer);
            }
        }
        return new Buffer[]{
                        key, value};
    }

    /**
     * Will check whether the next readable byte in the buffer is a certain byte
     *
     * @param buffer
     *            the buffer to peek into
     * @param b
     *            the byte we are checking is the next byte in the buffer to be
     *            read
     * @return true if the next byte to read indeed is what we hope it is
     */
    public static boolean isNext(final Buffer buffer, final byte b) throws IOException {

        return buffer.hasReadableBytes() && buffer.peekByte() == b;
    }

    /**
     * Check whether the next byte is a digit or not
     *
     * @param buffer
     * @retur
     * @throws IOException
     * @throws IndexOutOfBoundsException
     */
    public static boolean isNextDigit(final Buffer buffer) throws IndexOutOfBoundsException, IOException {
        if (buffer.hasReadableBytes()) {
            final char next = (char) buffer.peekByte();
            return next >= 48 && next <= 57;
        }

        return false;
    }

    /**
     * Will expect at least 1 digit and will continue consuming bytes until a
     * non-digit is encountered
     *
     * @param buffer
     *            the buffer to consume the digits from
     * @return the buffer containing digits only
     * @throws SipParseException
     *             in case there is not one digit at the first position in the
     *             buffer (where the current reader index is pointing to)
     * @throws IOException
     * @throws IndexOutOfBoundsException
     */
    public static Buffer expectDigit(final Buffer buffer) throws SipParseException {
        final int start = buffer.getReaderIndex();
        try {

            while (buffer.hasReadableBytes() && isNextDigit(buffer)) {
                // consume it
                buffer.readByte();
            }

            if (start == buffer.getReaderIndex()) {
                throw new SipParseException(start, "Expected digit");
            }

            return buffer.slice(start, buffer.getReaderIndex());
        } catch (final IndexOutOfBoundsException e) {
            throw new SipParseException(start, "Expected digit but no more bytes to read");
        } catch (final IOException e) {
            throw new SipParseException(start, "Expected digit unable to read from underlying stream");
        }
    }

    /**
     * Convenience method for expecting (and consuming) a HCOLON, which is
     * defined as:
     *
     * HCOLON = *( SP / HTAB ) ":" SWS
     *
     * See RFC3261 section 25.1 Basic Rules
     */
    public static int expectHCOLON(final Buffer buffer) throws SipParseException {
        final int consumed = expectHCOLONStreamFriendly(buffer);
        if (consumed == -1) {
            // -1 means we ran out of bytes in the stream but in those
            // cases where we are not really dealing with a stream we
            // would expect an IndexOutOfBoundsException so let's make
            // sure to still honor that...
            throw new IndexOutOfBoundsException();
        }
        return consumed;
    }

    /**
     * A problem with stream based protocols is that you don't have everything
     * available right away so e.g. when expecting HCOLON you may consume white spaces
     * but then you run out of bytes because you haven't received them yet
     * so therefore the expect(buffer, COLON) will blow up for the wrong reasons.
     * It may very well be that the next byte on the wire is indeed a ':' and as
     * such you should rather signal that you didn't have enough bytes to do your
     * job. This one does that.
     *
     * Note, still some issues with this one though. Let's say the header
     * we are trying to parse is "X-Hello :    whatever" and we have
     * only received "X-Hello :" so far. So first off we consume "X-Hello"
     * then call this method, which will consume " :" but ideally we also
     * want to consume the white space after the colon. I.e., when we
     * go off and consume the value of this header we will end up with
     * white space which normally should have been consumed by this method.
     *
     * Hence, whenever dealing with streams you will run into this issue
     * and will have to keep extra state around to solve it. See the
     * {@link SipMessageStreamBuilder} for how it is dealing with this.
     *
     * @param buffer
     * @return
     * @throws SipParseException
     */
    public static int expectHCOLONStreamFriendly(final Buffer buffer) throws SipParseException {
        try {
            int consumed = consumeWS(buffer);
            if (buffer.hasReadableBytes()) {
                expect(buffer, COLON);
            } else {
                // normally the expect(buffer, COLON) would have thrown
                // an IndexOutOfBoundsException but let's signal that as
                // -1
                return -1;
            }
            ++consumed;
            consumed += consumeSWSAfterHColon(buffer);
            return consumed;
        } catch (final IOException e) {
            throw new SipParseException(buffer.getReaderIndex(), UNABLE_TO_READ_FROM_STREAM, e);
        }
    }

    public static int consumeSWSAfterHColon(final Buffer buffer) throws IOException {
        // Part of the fix for empty header values. Which based on the BNF I don't
        // think is legal but it is certainly happening in the wild
        // so need to accept it.
        int consumed = consumeWS(buffer);
        if (!isNext(buffer, CR)) {
            consumed += consumeSWS(buffer);
        }

        return consumed;
    }

    /**
     * Convenience method for expecting (and consuming) a SLASH, which is
     * defined as:
     *
     *
     * See RFC3261 section 25.1 Basic Rules
     *
     * @param buffer
     * @throws SipParseException
     */
    public static void expectSLASH(final Buffer buffer) throws SipParseException {
        try {
            final int count = consumeSLASH(buffer);
            if (count == 0) {
                throw new SipParseException(buffer.getReaderIndex(), "Expected SLASH");
            }
        } catch (final IndexOutOfBoundsException e) {
            throw new SipParseException(buffer.getReaderIndex(), "Expected SLASH but nothing more to read", e);

        } catch (final IOException e) {
            throw new SipParseException(buffer.getReaderIndex(), "Expected SLASH but problem reading from stream", e);
        }
    }

    /**
     * Consume an slash (SLASH), which according to RFC3261 section 25.1 Basic
     * Rules is:
     *
     * SLASH = SWS "/" SWS ; slash
     *
     * @param buffer
     * @return true if we indeed did consume a SLASH
     * @throws IOException
     * @throws IndexOutOfBoundsException
     */
    public static int consumeSLASH(final Buffer buffer) throws SipParseException, IndexOutOfBoundsException,
                    IOException {
        return consumeSeparator(buffer, SLASH);
    }

    /**
     * Expect the next byte to be a white space
     *
     * @param buffer
     */
    public static int expectWS(final Buffer buffer) throws SipParseException {
        int consumed = 0;
        try {
            if (buffer.hasReadableBytes()) {
                final byte b = buffer.getByte(buffer.getReaderIndex());
                if (b == SP || b == HTAB) {
                    // ok, it was a WS so consume the byte
                    buffer.readByte();
                    ++consumed;
                } else {
                    throw new SipParseException(buffer.getReaderIndex(), "Expected WS");
                }
            } else {
                throw new SipParseException(buffer.getReaderIndex(),
                                            "Expected WS but nothing more to read in the buffer");
            }
        } catch (final IOException e) {
            throw new SipParseException(buffer.getReaderIndex(), UNABLE_TO_READ_FROM_STREAM, e);
        }
        return consumed;
    }

    /**
     * Consume sep (separating?) whitespace (LWS), which according to RFC3261
     * section 25.1 Basic Rules is:
     *
     * SWS = [LWS] ; sep whitespace
     *
     * "The SWS construct is used when linear white space is optional, generally
     * between tokens and separators." (RFC3261)
     *
     * @param buffer
     * @return
     */
    public static int consumeSWS(final Buffer buffer) {

        return consumeLWS(buffer);
    }

    /**
     * Consume an asterisk/star (STAR), which according to RFC3261 section 25.1
     * Basic Rules is:
     *
     * STAR = SWS "*" SWS ; asterisk
     *
     * @param buffer
     * @return true if we indeed did consume a STAR
     * @throws IOException
     * @throws IndexOutOfBoundsException
     */
    public static int consumeSTAR(final Buffer buffer) throws SipParseException, IndexOutOfBoundsException,
                    IOException {
        return consumeSeparator(buffer, STAR);
    }

    /**
     * Consume equal sign (EQUAL), which according to RFC3261 section 25.1 Basic
     * Rules is:
     *
     * EQUAL = SWS "=" SWS ; equal
     *
     * @param buffer
     * @return
     * @throws IOException
     * @throws IndexOutOfBoundsException
     */
    public static int consumeEQUAL(final Buffer buffer) throws IndexOutOfBoundsException, IOException {
        return consumeSeparator(buffer, EQ);
    }

    /**
     * Consume left parenthesis (LPAREN), which according to RFC3261 section
     * 25.1 Basic Rules is:
     *
     * LPAREN = SWS "(" SWS ; left parenthesis
     *
     * @param buffer
     * @return
     * @throws IOException
     * @throws IndexOutOfBoundsException
     */
    public static int consumeLPAREN(final Buffer buffer) throws IndexOutOfBoundsException, IOException {
        return consumeSeparator(buffer, LPAREN);
    }

    /**
     * Consume right parenthesis (RPAREN), which according to RFC3261 section
     * 25.1 Basic Rules is:
     *
     * RPAREN = SWS ")" SWS ; right parenthesis
     *
     * @param buffer
     * @return
     * @throws IOException
     * @throws IndexOutOfBoundsException
     */
    public static int consumeRPAREN(final Buffer buffer) throws IndexOutOfBoundsException, IOException {
        return consumeSeparator(buffer, RPAREN);
    }

    /**
     * Consume right angle quote (RAQUOT), which according to RFC3261 section
     * 25.1 Basic Rules is:
     *
     * RAQUOT = SWS ">"; left angle quote
     *
     * @param buffer
     * @return
     * @throws IOException
     * @throws IndexOutOfBoundsException
     */
    public static int consumeRAQUOT(final Buffer buffer) throws IndexOutOfBoundsException, IOException {
        return consumeSeparator(buffer, RAQUOT);
    }

    /**
     * Consume left angle quote (LAQUOT), which according to RFC3261 section
     * 25.1 Basic Rules is:
     *
     * LAQUOT  =  SWS "<"; left angle quote
     *
     * @param buffer
     * @return
     * @throws IOException
     * @throws IndexOutOfBoundsException
     */
    public static int consumeLAQUOT(final Buffer buffer) throws IndexOutOfBoundsException, IOException {
        return consumeSeparator(buffer, LAQUOT);
    }

    /**
     * Consume comma (COMMA), which according to RFC3261 section 25.1 Basic
     * Rules is:
     *
     * COMMA = SWS "," SWS ; comma
     *
     * @param buffer
     * @return true if we indeed did consume a COMMA
     * @throws IOException
     * @throws IndexOutOfBoundsException
     */
    public static int consumeCOMMA(final Buffer buffer) throws SipParseException, IndexOutOfBoundsException,
                    IOException {
        return consumeSeparator(buffer, COMMA);
    }

    /**
     * Consume semicolon (SEMI), which according to RFC3261 section 25.1 Basic
     * Rules is:
     *
     * SEMI = SWS ";" SWS ; semicolon
     *
     * @param buffer
     * @return true if we indeed did consume a SEMI
     * @throws IOException
     * @throws IndexOutOfBoundsException
     */
    public static int consumeSEMI(final Buffer buffer) throws SipParseException, IndexOutOfBoundsException,
                    IOException {
        return consumeSeparator(buffer, SEMI);
    }

    /**
     * Consume colon (COLON), which according to RFC3261 section 25.1 Basic
     * Rules is:
     *
     * COLON = SWS ":" SWS ; colon
     *
     * @param buffer
     * @return true if we indeed did consume a COLON
     * @throws IOException
     * @throws IndexOutOfBoundsException
     */
    public static int consumeCOLON(final Buffer buffer) throws SipParseException, IndexOutOfBoundsException,
                    IOException {
        return consumeSeparator(buffer, COLON);
    }

    /**
     * Consume open double quotation mark (LDQUT), which according to RFC3261
     * section 25.1 Basic Rules is:
     *
     * LDQUOT = SWS DQUOTE; open double quotation mark
     *
     * @param buffer
     * @return true if we indeed did consume a LDQUOT
     * @throws IOException
     * @throws IndexOutOfBoundsException
     */
    public static int consumeLDQUOT(final Buffer buffer) throws SipParseException, IndexOutOfBoundsException,
                    IOException {
        buffer.markReaderIndex();
        int consumed = consumeSWS(buffer);
        if (isNext(buffer, DQUOT)) {
            buffer.readByte();
            ++consumed;
        } else {
            buffer.resetReaderIndex();
            return 0;
        }
        return consumed;
    }

    /**
     * Consume close double quotation mark (RDQUT), which according to RFC3261
     * section 25.1 Basic Rules is:
     *
     * RDQUOT = DQUOTE SWS ; close double quotation mark
     *
     * @param buffer
     * @return true if we indeed did consume a LDQUOT
     * @throws IOException
     * @throws IndexOutOfBoundsException
     */
    public static int consumeRDQUOT(final Buffer buffer) throws SipParseException, IndexOutOfBoundsException,
                    IOException {
        buffer.markReaderIndex();
        int consumed = 0;
        if (isNext(buffer, DQUOT)) {
            buffer.readByte();
            ++consumed;
        } else {
            buffer.resetReaderIndex();
            return 0;
        }
        consumed += consumeSWS(buffer);
        return consumed;
    }

    /**
     * Helper function for checking stuff as described below. It is all the same pattern so...
     * (from rfc 3261 section 25.1)
     *
     * When tokens are used or separators are used between elements,
     * whitespace is often allowed before or after these characters:
     *
     * STAR    =  SWS "*" SWS ; asterisk
     * SLASH   =  SWS "/" SWS ; slash
     * EQUAL   =  SWS "=" SWS ; equal
     * LPAREN  =  SWS "(" SWS ; left parenthesis
     * RPAREN  =  SWS ")" SWS ; right parenthesis
     * RAQUOT  =  ">" SWS ; right angle quote
     * LAQUOT  =  SWS "<"; left angle quote
     * COMMA   =  SWS "," SWS ; comma
     * SEMI    =  SWS ";" SWS ; semicolon
     * COLON   =  SWS ":" SWS ; colon
     * LDQUOT  =  SWS DQUOTE; open double quotation mark
     * RDQUOT  =  DQUOTE SWS ; close double quotation mark
     * @param buffer
     * @param b
     * @return the number of bytes that was consumed.
     * @throws IOException
     * @throws IndexOutOfBoundsException
     */
    private static int consumeSeparator(final Buffer buffer, final byte b) throws IndexOutOfBoundsException,
                    IOException {
        buffer.markReaderIndex();
        int consumed = consumeSWS(buffer);
        if (isNext(buffer, b)) {
            buffer.readByte();
            ++consumed;
        } else {
            buffer.resetReaderIndex();
            return 0;
        }
        consumed += consumeSWS(buffer);
        return consumed;
    }

    /**
     * Expects a token, which according to RFC3261 section 25.1 Basic Rules is:
     *
     * token = 1*(alphanum / "-" / "." / "!" / "%" / "*" / "_" / "+" / "`" / "'"
     * / "~" )
     *
     * @param buffer
     * @return the buffer containing the expected token
     * @throws IOException
     * @throws IndexOutOfBoundsException
     * @throws SipParseException
     *             in case there is no token
     */
    public static Buffer expectToken(final Buffer buffer) throws IndexOutOfBoundsException, IOException,
                    SipParseException {
        final Buffer token = consumeToken(buffer);
        if (token == null) {
            throw new SipParseException(buffer.getReaderIndex(), "Expected TOKEN");
        }
        return token;
    }

    /**
     * Consumes a quoted-string, which is defined as:
     *
     * <pre>
     * quoted-string  =  SWS DQUOTE *(qdtext / quoted-pair ) DQUOTE
     * qdtext         =  LWS / %x21 / %x23-5B / %x5D-7E / UTF8-NONASCII
     * quoted-pair    =  "\" (%x00-09 / %x0B-0C / %x0E-7F)
     * </pre>
     *
     * Note, this is a somewhat simplified version and we'll
     *
     * @param buffer
     * @return
     * @throws IOException
     * @throws SipParseException
     */
    public static Buffer consumeQuotedString(final Buffer buffer) throws SipParseException, IOException {
        final int start = buffer.getReaderIndex();
        expect(buffer, DQUOT);
        while (buffer.hasReadableBytes()) {
            final byte b = buffer.readByte();
            if (b == DQUOT) {
                break;
            } else if (b == BACK_SLASH) {
                buffer.readByte();
            }
        }
        final int stop = buffer.getReaderIndex();
        buffer.setReaderIndex(start + 1);
        final Buffer result = buffer.readBytes(stop - start - 2);
        buffer.setReaderIndex(stop);
        return result;
    }

    /**
     * The display name in SIP is a little tricky since it may or may not be
     * there and the stuff following it (whether or not it was there to begin
     * with) can easily be confused with being a display name.
     *
     * Note, a display name in SIP is only part of of the "name-addr" construct
     * and this function assumes that (even though it actually would work like a
     * regular {@link #consumeToken(Buffer)} in some cases)
     *
     * <pre>
     * name-addr      =  [ display-name ] LAQUOT addr-spec RAQUOT
     * display-name   =  *(token LWS)/ quoted-string
     * </pre>
     *
     * @param buffer
     * @return the display name or null if there was none
     * @throws IOException
     * @throws SipParseException
     */
    public static Buffer consumeDisplayName(final Buffer buffer) throws IOException, SipParseException {

        return consumeDisplayName(isNext(buffer, DQUOT), buffer);
    }

    /**
     * The display name in SIP is a little tricky since it may or may not be
     * there and the stuff following it (whether or not it was there to begin
     * with) can easily be confused with being a display name.
     *
     * Note, a display name in SIP is only part of of the "name-addr" construct
     * and this function assumes that (even though it actually would work like a
     * regular {@link #consumeToken(Buffer)} in some cases)
     *
     * <pre>
     * name-addr      =  [ display-name ] LAQUOT addr-spec RAQUOT
     * display-name   =  *(token LWS)/ quoted-string
     * </pre>
     *
     * @param quoted
     * @param buffer
     * @return the display name or null if there was none
     * @throws IOException
     * @throws SipParseException
     */
    public static Buffer consumeDisplayName(final boolean quoted, final Buffer buffer) throws IOException, SipParseException {
        if (quoted) {
            return consumeQuotedString(buffer);
        }

        final int count = getTokenCount(buffer);
        if (count == 0) {
            return Buffers.EMPTY_BUFFER;
        }

        // now, if the next thing after the count is a ':' then
        // we mistook the scheme for a token so bail out.
        buffer.markReaderIndex();
        final Buffer potentialDisplayName = buffer.readBytes(count);
        if (isNext(buffer, COLON)) {
            buffer.resetReaderIndex();
            return Buffers.EMPTY_BUFFER;
        }

        // all good...
        return potentialDisplayName;
    }

    public static Buffer consumeAddressSpec(final Buffer buffer) throws IndexOutOfBoundsException, IOException,
                    SipParseException {
        return consumeAddressSpec(false, buffer);
    }

    /**
     * Consumes addr-spec, which according to RFC3261 section 25.1 is:
     *
     * <pre>
     * addr-spec      =  SIP-URI / SIPS-URI / absoluteURI
     * SIP-URI          =  "sip:" [ userinfo ] hostport
     *                      uri-parameters [ headers ]
     * SIPS-URI         =  "sips:" [ userinfo ] hostport
     *                      uri-parameters [ headers ]
     *
     * absoluteURI    =  scheme ":" ( hier-part / opaque-part )
     * </pre>
     * <p>
     * And as you can see, it gets complicated. Also, these consume-functions
     * are not to validate the exact grammar but rather to find the boundaries
     * so the strategy for consuming the addr-spec is:
     * <ul>
     * <li>If '>' is encountered, then we assume that this addr-spec is within a
     * name-addr so we stop here</li>
     * <li>If a white space or end-of-line is encountered, we also assume we are
     * done UNLESS the calling code knows that we are in a protected "< >" then
     * we should allow for white space. See issue-106 </li>
     * </ul>
     *
     * <p>
     * Because of <a href="https://github.com/aboutsip/pkts/issues/106">issue-106</a> the <code>isProtected</code>
     * boolean was introduced to allow for spaces after the initial ";' when we know the entire
     * <code>addr-spec</code> is e.g. "protected" inside of angle brackets "< >", which is often the case
     * when we parse out a <code>name-addr</code>, as we do in {@link io.pkts.packet.sip.address.Address#frame(Buffer)}.
     * </p>
     *
     * @return
     * @throws IOException
     * @throws IndexOutOfBoundsException
     * @throws SipParseException         in case we cannot successfully frame the addr-spec.
     */
    public static Buffer consumeAddressSpec(final boolean isProtected, final Buffer buffer) throws IndexOutOfBoundsException, IOException,
                    SipParseException {
        final int startIndex = buffer.getReaderIndex();
        int count = 0;
        int state = 0; // zero is to look for colon, everything else is to find the end
        boolean done = false;

        while (buffer.hasReadableBytes() && !done) {
            ++count;
            final byte b = buffer.readByte();

            // emergency breaks...
            if (state == 0 && count > 99) {
                throw new SipParseException(buffer.getReaderIndex(), "No scheme found after 100 bytes, giving up");
            } else if (count > MAX_LOOK_AHEAD) {
                throw new SipParseException(buffer.getReaderIndex(),
                                            "Have not been able to find the entire addr-spec after " + count + " bytes, giving up");
            } else if (state == 0 && b == COLON) {
                state = 1;
            } else if (state == 1 && (b == RAQUOT || b == CR || b == LF)) {
                done = true;
                --count;
            } else if (!isProtected && state == 1 && (b == SP || b == HTAB)) {
                // https://github.com/aboutsip/pkts/issues/106
                done = true;
                --count;
            }
        }
        buffer.setReaderIndex(startIndex);

        // didn't find the scheme portion
        if (state == 0) {
            throw new SipParseException(buffer.getReaderIndex(), "No scheme found");
        }

        if (count > 0) {
            return buffer.readBytes(count);
        }

        return null;
    }

    /**
     * Consume the userinfo and hostport.
     *
     * The reason why this method does both is because the userinfo portion is
     * optional and as such you actually don't know whether the stuff you are
     * currently going over is the hostport or the userinfo. Also, unfortunately
     * the userinfo is allowed to have characters that normally are reserved
     * (such as ';'), which complicates things as well.
     *
     *
     * <pre>
     * SIP-URI  =  "sip:" [ userinfo ] hostport
     *             uri-parameters [ headers ]
     * SIPS-URI =  "sips:" [ userinfo ] hostport
     *             uri-parameters [ headers ]
     *
     * userinfo         =  ( user / telephone-subscriber ) [ ":" password ] "@"
     * user             =  1*( unreserved / escaped / user-unreserved )
     * user-unreserved  =  "&" / "=" / "+" / "$" / "," / ";" / "?" / "/"
     * password         =  *( unreserved / escaped / "&" / "=" / "+" / "$" / "," )
     * hostport         =  host [ ":" port ]
     * </pre>
     *
     * As you can see, the user-unreserved is what mess things up. it would have
     * been way easier and more efficient if the user-unreserved wasn't there...
     *
     * @param buffer
     * @return
     */
    public static SipUserHostInfo consumeUserInfoHostPort(final Buffer buffer) throws SipParseException, IOException {
        return SipUserHostInfo.frame(buffer);
    }

    /**
     * Consume the "sent-protocol", which according to RFC 3261 is:
     *
     * <pre>
     * sent-protocol = protocol-name SLASH protocol-version SLASH transport
     * transport     =  "UDP" / "TCP" / "TLS" / "SCTP" / other-transport
     * other-transport   =  token
     * </pre>
     *
     * The "sent-protocol" is only present in a Via header and typically looks
     * like this: SIP/2.0/UDP
     *
     * The consume method will make sure that "SIP/2.0/" is present and if not,
     * complain. The transport can really be anything and as such that is what
     * you will get back as a return value. Hence, in the above example you
     * would get back a buffer consisting of "UDP".
     *
     * Also note that the transport can be "other-transport" which translates to
     * a "token" so we allow really anything and as such we will just consume
     * and return the token after verifying that it start with the SIP/2.0
     * stuff.
     *
     * @param buffer
     * @return the transport part of the "sent-protocol". Typically this will be
     *         one of UDP, TCP or TLS.
     * @throws IOException
     * @throws SipParseException
     *             in case anything goes wrong while parsing including if the
     *             protocol-name isn't SIP and the version isn't 2.0
     */
    public static Buffer consumeSentProtocol(final Buffer buffer) throws IOException, SipParseException {
        expectSIP2_0(buffer);
        expect(buffer, SipParser.SLASH);

        final Buffer protocol = consumeToken(buffer);
        if (protocol == null || protocol.isEmpty()) {
            throw new SipParseException(buffer.getReaderIndex(), "Expected transport");
        }
        return protocol;
    }

    /**
     * Consume a token, which according to RFC3261 section 25.1 Basic Rules is:
     *
     * token = 1*(alphanum / "-" / "." / "!" / "%" / "*" / "_" / "+" / "`" / "'"
     * / "~" )
     *
     * @param buffer
     * @return the buffer containing the token we consumed or null if nothing
     *         was consumed.
     * @throws IOException
     * @throws IndexOutOfBoundsException
     */
    public static Buffer consumeToken(final Buffer buffer) throws IndexOutOfBoundsException, IOException {
        final int count = getTokenCount(buffer);
        if (count == 0) {
            return null;
        }
        return buffer.readBytes(count);
    }

    /**
     * Consumes a alphanum.
     *
     * @param buffer
     * @return
     * @throws IOException
     */
    public static Buffer consumeAlphaNum(final Buffer buffer) throws IOException {
        final int count = getAlphaNumCount(buffer);
        if (count == 0) {
            return null;
        }
        return buffer.readBytes(count);
    }

    /**
     * Consume a Via-header, which according to RFC3261 is:
     *
     * <pre>
     * Via               =  ( "Via" / "v" ) HCOLON via-parm *(COMMA via-parm)
     * via-parm          =  sent-protocol LWS sent-by *( SEMI via-params )
     * via-params        =  via-ttl / via-maddr / via-received / via-branch / via-extension
     * via-ttl           =  "ttl" EQUAL ttl
     * via-maddr         =  "maddr" EQUAL host
     * via-received      =  "received" EQUAL (IPv4address / IPv6address)
     * via-branch        =  "branch" EQUAL token
     * via-extension     =  generic-param
     * sent-protocol     =  protocol-name SLASH protocol-version SLASH transport
     * </pre>
     *
     * Note, this method assumes that you have already stripped off the "Via" or
     * "v". I.e., the header name.
     *
     * The return value is ALWAYS an array of objects of size 4. However, the
     * port can be null (index 2 = the third value) so make sure to check it.
     * Also, the 4th value (index 3) contains a List<Buffer[]> containing all
     * the via-parameters. Each entry in the list is a two dimensional array
     * where the first element is the key and the second is the value. For flag
     * parameters, the value will be null so make sure to check. For more info
     * regarding how parameters are parsed, see
     * {@link #consumeGenericParams(Buffer)}.
     *
     * <ul>
     * <li>result[0] - the protocol. Will never ever be null</li>
     * <li>result[1] - the sent-by host. Will never ever be null</li>
     * <li>result[2] - the sent-by port. May be null if port wasn't specified</li>
     * <li>result[3] - the via-parameters as a List<Buffer[]>. Will always have
     * elements in it since a via without a branch parameter is illegal.</li>
     * </ul>
     *
     * @param buffer
     * @return returns an array of 4 elements. See above for details
     * @throws SipParseException
     * @throws IOException
     */
    public static Object[] consumeVia(final Buffer buffer) throws SipParseException, IOException {
        final Object[] result = new Object[4];
        // start off by just finding the ';'. A Via-header MUST have a branch
        // parameter and as such
        // there must be a ';' present. If there isn't one, then bail out and
        // complain.
        int count = 0;
        int indexOfSemi = 0;
        int countOfColons = 0;
        int indexOfLastColon = 0;
        int readerIndexOfLastColon = 0; // for reporting

        consumeSWS(buffer);
        result[0] = consumeSentProtocol(buffer);
        if(consumeLWS(buffer) == 0){
            throw new SipParseException(buffer.getReaderIndex(), "Expected at least 1 WSP");
        }

        final int index = buffer.getReaderIndex();
        while (indexOfSemi == 0 && buffer.hasReadableBytes() && ++count < MAX_LOOK_AHEAD) {
            final byte b = buffer.readByte();
            if (b == SipParser.SEMI) {
                indexOfSemi = count;
            } else if (b == SipParser.COLON) {
                ++countOfColons;
                indexOfLastColon = count;
                readerIndexOfLastColon = buffer.getReaderIndex();
            }
        }

        if (count == 0) {
            return null;
        }

        if (count == MAX_LOOK_AHEAD) {
            throw new SipParseException(buffer.getReaderIndex(),
                                        "Unable to find the parameters part of the Via-header "
                                        + "even after searching for " + MAX_LOOK_AHEAD + " bytes.");
        }

        if (indexOfSemi == 0) {
            // well, we don't check if the branch parameter is there but
            // if there are no parameters present at all then there
            // is no chance of it being present.
            throw new SipParseException(buffer.getReaderIndex(),
                                        "No via-parameters found. The Via-header MUST contain at least the branch parameter.");
        }

        buffer.setReaderIndex(index);

        if (countOfColons == 0 || countOfColons == 7) {
            // no port, just host
            result[1] = buffer.readBytes(indexOfSemi - 1);
        } else if (indexOfLastColon != 0 && (countOfColons == 1 || countOfColons == 8)) {
            // found either a single colon or 8 colons where 8 colons indicates
            // that we have an ipv6 address in front of us.
            result[1] = buffer.readBytes(indexOfLastColon - 1);
            buffer.readByte(); // consume ':'
            result[2] = buffer.readBytes(indexOfSemi - indexOfLastColon - 1); // consume
            // port
            if (result[2] == null || ((Buffer) result[2]).isEmpty()) {
                throw new SipParseException(readerIndexOfLastColon + 1, "Expected port after colon");
            }
        } else {
            // indication an strange number of colons. May be the strange
            // ipv4 address after ipv6 thing which we currently dont handle
            throw new SipParseException(indexOfLastColon, "Found " + countOfColons + " which seems odd."
                                                          + " Expecting 0, 1, 7 or 8 colons in the Via-host:port portion. Please check your traffic");
        }

        final List<Buffer[]> params = consumeGenericParams(buffer);
        result[3] = params;

        return result;
    }

    /**
     * Consume a sent-by which according to 3261 is:
     *
     * <pre>
     * sent-by           =  host [ COLON port ]
     * host             =  hostname / IPv4address / IPv6reference
     * hostname         =  *( domainlabel "." ) toplabel [ "." ]
     * domainlabel      =  alphanum
     * toplabel         =  ALPHA / ALPHA *( alphanum / "-" ) alphanum
     * IPv4address    =  1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT
     * IPv6reference  =  "[" IPv6address "]"
     * IPv6address    =  hexpart [ ":" IPv4address ]
     * hexpart        =  hexseq / hexseq "::" [ hexseq ] / "::" [ hexseq ]
     * hexseq         =  hex4 *( ":" hex4)
     * hex4           =  1*4HEXDIG
     * port           =  1*DIGIT
     * </pre>
     *
     * Now, since we want to do things as fast as possible and all the
     * consumeXXX methods only do framing we will implement something a little
     * simpler, faster but not 100% according to the BNF.
     *
     * So, consume everything until we either hit a ';' signifying parameters or
     * a ':' which then forces us to start checking the port. Also, white space
     * will stop parsing.
     *
     * However, a colon could also signify an IPv6 address, which is not handled
     * right now.
     *
     * @param buffer
     * @return
     * @throws IOException
     */
    public static Buffer[] consumeSentBye(final Buffer buffer) throws SipParseException, IOException {
        final int index = buffer.getReaderIndex();
        int count = 0;
        boolean done = false;
        boolean firstColon = false;
        boolean consumePort = false;
        while (!done && buffer.hasReadableBytes()) {
            final byte b = buffer.readByte();
            ++count;
            if (firstColon && isDigit(b)) {
                // get port
                consumePort = true;
                done = true;
                count -= 2;
                continue;
            } else if (firstColon) {
                // consume
                firstColon = false;
                continue;
            }

            if (b == SipParser.COLON) {
                firstColon = true;
            } else if (b == SipParser.SEMI) {
                --count;
                done = true;
            }
        }

        if (count == 0) {
            return null;
        }

        buffer.setReaderIndex(index);
        final Buffer host = buffer.readBytes(count);
        Buffer port = null;
        if (consumePort) {
            buffer.readByte(); // consume ':'
            port = consumePort(buffer);
        }
        return new Buffer[]{
                        host, port};
    }

    /**
     * Consume a port, which according to RFC 3261 is:
     *
     * port = 1*DIGIT
     *
     * @param buffer
     * @return the buffer containing only digits or null if there was none fun.
     * @throws IOException
     */
    public static Buffer consumePort(final Buffer buffer) throws IOException {
        int count = 0;
        final int index = buffer.getReaderIndex();
        boolean done = false;
        while (!done && buffer.hasReadableBytes()) {
            if (isDigit(buffer.readByte())) {
                ++count;
            } else {
                done = true;
            }
        }

        buffer.setReaderIndex(index);
        if (count != 0) {
            return buffer.readBytes(count);
        }

        return null;
    }

    public static Buffer consumeHostname(final Buffer buffer) throws IOException {
        return null;
    }

    /**
     * Consume a m-type, which according to RFC3261 section 25.1 Basic Rules is:
     *
     * <pre>
     * m-type           =  discrete-type / composite-type
     * discrete-type    =  "text" / "image" / "audio" / "video" / "io.sipstack.application.application" / extension-token
     * composite-type   =  "message" / "multipart" / extension-token
     * extension-token  =  ietf-token / x-token
     * ietf-token       =  token
     * x-token          =  "x-" token
     * </pre>
     *
     * And if you really read through all of that stuff it all boils down to
     * "token" so that is all we end up doing in this method. Hence, this method
     * is really only to put some context to consuming a media-type
     *
     * @return
     * @throws IndexOutOfBoundsException
     * @throws IOException
     */
    public static Buffer consumeMType(final Buffer buffer) throws SipParseException {
        try {
            return consumeToken(buffer);
        } catch (final IndexOutOfBoundsException e) {
            throw new SipParseException(buffer.getReaderIndex(), "Tried to consume m-type but buffer ended abruptly", e);
        } catch (final IOException e) {
            throw new SipParseException(buffer.getReaderIndex(),
                                        "Tried to consume m-type but problem reading from underlying stream", e);
        }
    }

    /**
     * Consume a m-subtype, which according to RFC3261 section 25.1 Basic Rules
     * is:
     *
     * <pre>
     * m-subtype        =  extension-token / iana-token
     * extension-token  =  ietf-token / x-token
     * ietf-token       =  token
     * x-token          =  "x-" token
     * iana-token       =  token
     * </pre>
     *
     * And if you really read through all of that stuff it all boils down to
     * "token" so that is all we end up doing in this method. Hence, this method
     * is really only to put some context to consuming a media-type
     *
     * @return
     * @throws IndexOutOfBoundsException
     * @throws IOException
     */
    public static Buffer consumeMSubtype(final Buffer buffer) throws SipParseException {
        try {
            return consumeToken(buffer);
        } catch (final IndexOutOfBoundsException e) {
            throw new SipParseException(buffer.getReaderIndex(), "Tried to consume m-type but buffer ended abruptly", e);
        } catch (final IOException e) {
            throw new SipParseException(buffer.getReaderIndex(),
                                        "Tried to consume m-type but problem reading from underlying stream", e);
        }
    }

    /**
     * Helper method that counts the number of bytes that are considered part of
     * the next token in the {@link Buffer}.
     *
     * @param buffer
     * @return a count of the number of bytes the next token contains or zero if
     *         no token is to be found within the buffer.
     * @throws IOException
     * @throws IndexOutOfBoundsException
     */
    public static int getTokenCount(final Buffer buffer) throws IndexOutOfBoundsException, IOException {
        boolean done = false;
        int count = 0;
        buffer.markReaderIndex();
        while (buffer.hasReadableBytes() && !done) {
            final byte b = buffer.readByte();
            final boolean ok = isAlphaNum(b) || b == DASH || b == PERIOD || b == EXCLAMATIONPOINT
                               || b == PERCENT || b == STAR || b == UNDERSCORE || b == PLUS || b == BACKTICK
                               || b == TICK || b == TILDE;
            if (ok) {
                ++count;
            } else {
                done = true;
            }
        }
        buffer.resetReaderIndex();
        return count;
    }

    /**
     * Helper method that counts the number of bytes that are considered part of
     * the next alphanum block.
     *
     * @param buffer
     * @return a count of the number of bytes the next alphaum contains or zero
     *         if none is found.
     * @throws IndexOutOfBoundsException
     * @throws IOException
     */
    public static int getAlphaNumCount(final Buffer buffer) throws IndexOutOfBoundsException, IOException {
        boolean done = false;
        int count = 0;
        final int index = buffer.getReaderIndex();
        while (buffer.hasReadableBytes() && !done) {
            final byte b = buffer.readByte();
            if (isAlphaNum(b)) {
                ++count;
            } else {
                done = true;
            }
        }
        buffer.setReaderIndex(index);
        return count;
    }

    /**
     * Check whether next byte is a alpha numeric one.
     *
     * @param buffer
     * @return true if the next byte is a alpha numeric character, otherwise
     *         false
     * @throws IOException
     * @throws IndexOutOfBoundsException
     */
    public static boolean isNextAlphaNum(final Buffer buffer) throws IndexOutOfBoundsException, IOException {
        if (buffer.hasReadableBytes()) {
            final byte b = buffer.peekByte();
            return isAlphaNum(b);
        }

        return false;
    }

    /**
     * Checks whether the character could be part of the host portion of a SIP URI.
     *
     * This does not perform an entirely robust validation as it does not operate
     * with the full context of the hostname.
     *
     * @param ch
     * @return true if the character is valid in hostnames, false otherwise
     */
    public static boolean isHostPortCharacter(final char ch) {
        return isAlphaNum(ch) || ch == DASH || ch == PERIOD || ch == COLON;
    }

    public static boolean isHostPortCharacter(final byte b) {
        return isHostPortCharacter((char) b);
    }

    /**
     * Helper method for checking whether the supplied byte is a alphanumeric
     * character or not.
     *
     * @param ch
     * @return true if the byte is indeed a alphanumeric character, false
     *         otherwise
     */
    public static boolean isAlphaNum(final char ch) {
        return ch >= 97 && ch <= 122 || ch >= 48 && ch <= 57 || ch >= 65 && ch <= 90;
    }

    public static boolean isAlphaNum(final byte b) {
        return isAlphaNum((char) b);
    }

    public static boolean isDigit(final char ch) {
        return ch >= 48 && ch <= 57;
    }

    public static boolean isDigit(final byte b) {
        return isDigit((char) b);
    }

    public static boolean isHexDigit(final char ch) {
        return ch >= 48 && ch <= 57 || ch >= 65 && ch <= 70 || ch >= 97 && ch <= 102;
    }

    public static boolean isHexDigit(final byte b) {
        return isHexDigit((char) b);
    }

    public static boolean isAlpha(final char ch) {
        return ch >= 97 && ch <= 122 || ch >= 65 && ch <= 90;
    }

    public static boolean isAlpha(final byte b) {
        return isAlpha((char) b);
    }

    /**
     * Consume linear whitespace (LWS), which according to RFC3261 section 25.1
     * Basic Rules is:
     *
     * LWS = [*WSP CRLF] 1*WSP ; linear whitespace
     *
     * @param buffer
     * @return the number of bytes consumed
     */
    public static int consumeLWS(final Buffer buffer) throws SipParseException {
        final int i = buffer.getReaderIndex();
        consumeWS(buffer);

        // if we consume a CRLF we expect at least ONE WS to be present next
        if (consumeCRLF(buffer) > 0) {
            expectWS(buffer);
        }
        consumeWS(buffer);
        if (buffer.getReaderIndex() == i) {
            return 0;
        }
        return buffer.getReaderIndex() - i;
    }

    /**
     * Consume CR + LF
     *
     * @param buffer
     * @return the number of bytes we consumed, which should be two
     * if we indeed consumed CRLF or zero otherwise.
     */
    public static int consumeCRLF(final Buffer buffer) throws SipParseException {
        try {
            buffer.markReaderIndex();
            if (buffer.readByte() == CR && buffer.readByte() == LF) {
                return 2;
            }
        } catch (final IndexOutOfBoundsException e) {
            // fall through
        } catch (final IOException e) {
            throw new SipParseException(buffer.getReaderIndex(), UNABLE_TO_READ_FROM_STREAM, e);
        }
        buffer.resetReaderIndex();
        return 0;
    }

    /**
     * Check so that the next byte in the passed in buffer is the expected one.
     *
     * @param buffer
     *            the buffer that we will check.
     * @param expected
     *            the buffer that contains the expected byte (note, it is 1
     *            byte, not bytes)
     * @throws ParseException
     *             in case the expected byte is not the next byte in the buffer
     */
    public static void expect(final Buffer buffer, final byte expected) throws SipParseException, IOException {
        final byte actual = buffer.readByte();
        if (actual != expected) {
            final String actualStr = new String(new byte[]{actual}, StandardCharsets.UTF_8);
            final String expectedStr = new String(new byte[]{expected});
            throw new SipParseException(buffer.getReaderIndex(), "Expected '" + expected + "' (" + expectedStr
                                                                 + ") got '" + actual + "' (" + actualStr + ")");
        }
    }

    /**
     * Consume all the whitespace we find (WS)
     *
     * @param buffer
     * @return true if we did consume something, false otherwise
     */
    public static int consumeWS(final Buffer buffer) throws SipParseException {
        try {
            int count = 0;
            while (buffer.hasReadableBytes()) {
                if (!isNext(buffer, SP) && !isNext(buffer, HTAB)) {
                    return count;
                }

                buffer.readByte();
                ++count;
            }
            return count;
        } catch (final IOException e) {
            throw new SipParseException(buffer.getReaderIndex(), UNABLE_TO_READ_FROM_STREAM, e);
        }
    }

    /**
     * Check so that the next byte in the passed in buffer is the expected one.
     *
     * @param buffer
     *            the buffer that we will check.
     * @param ch
     *            the expected char
     * @throws SipParseException
     *             in case the expected char is not the next char in the buffer
     *             or if there is an error reading from the underlying stream
     */
    public static void expect(final Buffer buffer, final char ch) throws SipParseException {
        try {
            final int i = buffer.readUnsignedByte();
            if (i != ch) {
                throw new SipParseException(buffer.getReaderIndex(), "Expected '" + ch + "' got '" + i + "'");
            }
        } catch (final IOException e) {
            throw new SipParseException(buffer.getReaderIndex(), UNABLE_TO_READ_FROM_STREAM, e);
        }
    }

    /**
     * Convenience method for slicing out the next header name but NOT checking
     * if HCOLON shows up afterwards. This is useful when dealing with a stream
     * of bytes coming in over the network, as in TCP, so you may actually find
     * SP or HTAB but the COLON, and as such, the HCOLON, hasn't shown up yet
     * so the method {@link #nextHeaderName(Buffer)} would blow up on no
     * HCOLON, which is not what we want. Hence, use this method and then
     * check HCOLON yourself.
     *
     * @param buffer
     * @return
     */
    public static Buffer nextHeaderNameDontCheckHColon(final Buffer buffer) {
        try {
            final int startIndex = buffer.getReaderIndex();
            int nameIndex = 0;
            while (buffer.hasReadableBytes() && nameIndex == 0) {
                if (isNext(buffer, SP) || isNext(buffer, HTAB) || isNext(buffer, COLON)) {
                    nameIndex = buffer.getReaderIndex();
                } else {
                    buffer.readByte();
                }
            }

            // Bad header! No HCOLON found! (or beginning thereof anyway)
            if (nameIndex == 0) {
                // probably ran out of bytes to read so lets just return null
                buffer.setReaderIndex(startIndex);
                return null;
            }

            return buffer.slice(startIndex, nameIndex);
        } catch (final IOException e) {
            throw new SipParseException(buffer.getReaderIndex(), UNABLE_TO_READ_FROM_STREAM, e);
        }

    }

    public static Buffer nextHeaderName(final Buffer buffer) throws SipParseException {
        final Buffer name = nextHeaderNameDontCheckHColon(buffer);
        if (name != null) {
            expectHCOLON(buffer);
        }
        return name;
    }

    public static List<SipHeader> nextHeaders(final Buffer buffer) throws SipParseException {
        try {
            final int startIndex = buffer.getReaderIndex();
            int nameIndex = 0;
            while (buffer.hasReadableBytes() && nameIndex == 0) {
                if (isNext(buffer, SP) || isNext(buffer, HTAB) || isNext(buffer, COLON)) {
                    nameIndex = buffer.getReaderIndex();
                } else {
                    buffer.readByte();
                }
            }

            // Bad header! No HCOLON found! (or beginning thereof anyway)
            if (nameIndex == 0) {
                // probably ran out of bytes to read so lets just return null
                return null;
            }

            final Buffer name = buffer.slice(startIndex, nameIndex);
            expectHCOLON(buffer);
            final List<Buffer> values = readHeaderValues(name, buffer).values;
            final List<SipHeader> headers = new ArrayList<SipHeader>(values.size());
            for (final Buffer value : values) {
                headers.add(new SipHeaderImpl(name, value));
            }
            return headers;
        } catch (final IOException e) {
            throw new SipParseException(buffer.getReaderIndex(), UNABLE_TO_READ_FROM_STREAM, e);
        }
    }

    /**
     * Not all headers allow for multiple values on a single line. This is a
     * basic check for validating whether or not that the header allows it or
     * not. Note, for headers such as Contact, it depends!
     *
     * @param headerName
     * @return
     */
    private static boolean isHeaderAllowingMultipleValues(final Buffer headerName) {
        final int size = headerName.getReadableBytes();
        if (size == 7) {
            return !isSubjectHeader(headerName);
        } else if (size == 5) {
            return !isAllowHeader(headerName);
        } else if (size == 4) {
            return !isDateHeader(headerName);
        } else if (size == 1) {
            return !isAllowEventsHeaderShort(headerName);
        } else if (size == 12) {
            return !isAllowEventsHeader(headerName);
        }

        return true;
    }

    /**
     * The date header also allows for comma within the value of the header.
     *
     * @param name
     * @return
     */
    private static boolean isDateHeader(final Buffer name) {
        try {
            return name.getByte(0) == 'D' && name.getByte(1) == 'a' &&
                   name.getByte(2) == 't' && name.getByte(3) == 'e';
        } catch (final IOException e) {
            return false;
        }
    }

    private static boolean isAllowHeader(final Buffer name) {
        try {
            return name.getByte(0) == 'A' && name.getByte(1) == 'l' &&
                   name.getByte(2) == 'l' && name.getByte(3) == 'o' &&
                   name.getByte(4) == 'w';
        } catch (final IOException e) {
            return false;
        }
    }

    private static boolean isAllowEventsHeaderShort(final Buffer name) {
        try {
            return name.getByte(0) == 'u';
        } catch (final IOException e) {
            return false;
        }
    }

    private static boolean isAllowEventsHeader(final Buffer name) {
        try {
            return name.getByte(0) == 'A' && name.getByte(1) == 'l' &&
                   name.getByte(2) == 'l' && name.getByte(3) == 'o' &&
                   name.getByte(4) == 'w' && name.getByte(5) == '-' &&
                   name.getByte(6) == 'E' && name.getByte(7) == 'v' &&
                   name.getByte(8) == 'e' && name.getByte(9) == 'n' &&
                   name.getByte(10) == 't' && name.getByte(11) == 's';
        } catch (final IOException e) {
            return false;
        }
    }

    private static boolean isSubjectHeader(final Buffer name) {
        try {
            return name.getByte(0) == 'S' && name.getByte(1) == 'u' &&
                   name.getByte(2) == 'b' && name.getByte(3) == 'j' &&
                   name.getByte(4) == 'e' && name.getByte(5) == 'c' &&
                   name.getByte(6) == 't';
        } catch (final IOException e) {
            return false;
        }
    }

    /**
     * Because parsing streaming data where not everything has shown up yet we must remember
     * the state of where we are in the "parse out header values".
     */
    public static final class HeaderValueState {
        public List<Buffer> values = new ArrayList<>(2);
        public int start;
        public int stop = -1;
        public boolean foundCR = false;
        public boolean foundLF = false;
        public boolean foundCRLF = false;
        public boolean foundComma = false;
        public boolean insideQuotedString = false;
        public boolean done = false;
        public boolean foldedLine = false;

        public HeaderValueState(final int readerIndex) {
            start = readerIndex;
        }

        public void reset(final int readerIndex) {
            start = readerIndex;
            values = new ArrayList<>(2);
            stop = -1;
            foundCR = false;
            foundLF = false;
            foundCRLF = false;
            foundComma = false;
            insideQuotedString = false;
            done = false;
            foldedLine = false;
        }

    }

    public static HeaderValueState readHeaderValues(final HeaderValueState state, final Buffer headerName, final Buffer buffer) throws IOException {
        while (buffer.hasReadableBytes() && !state.done) {
            final byte b = buffer.readByte();
            switch (b) {
                case DOUBLE_QOUTE:
                    state.insideQuotedString = !state.insideQuotedString;
                    break;
                case LF:
                    state.foundLF = true;
                    state.foundCRLF = state.foundCR;
                    final int separatorLen = state.foundCRLF ? 2 : 1;
                    state.stop = buffer.getReaderIndex() - separatorLen;
                    break;
                case CR:
                    state.foundCR = true;
                    // state.stop = buffer.getReaderIndex() - 1;
                    break;
                case COMMA:
                    // if we find a comma then we may have found the end of this
                    // header value depending whether or not the header we are
                    // framing actually allows multiple values on a single line
                    if (!state.insideQuotedString && isHeaderAllowingMultipleValues(headerName)) {
                        state.stop = buffer.getReaderIndex() - 1;
                        buffer.setReaderIndex(state.stop);
                        consumeCOMMA(buffer);
                        state.foundComma = true;
                        state.foundCRLF = true;
                    }
                    break;
                default:
                    break;
            }

            if (state.foundCRLF || state.foundLF) {
                state.values.add(buffer.slice(state.start, state.stop));
                // if (isNext(buffer, LF)) {
                // buffer.readByte();
                // }
                // Until we have implemented the CompositeBuffer I'm just going to cheat like this
                // and yes, it is stupid but folded lines aren't that common so...
                if (state.foldedLine) {
                    final Buffer line2 = state.values.remove(state.values.size() - 1);
                    final Buffer line1 = state.values.remove(state.values.size() - 1);
                    final Buffer folded = Buffers.wrap(line1 + " " + line2);
                    state.values.add(folded);
                    state.foldedLine = false;
                }

                if (isNext(buffer, SP) || isNext(buffer, HTAB)) {
                    consumeWS(buffer);
                    state.foldedLine = !state.foundComma;
                } else if (!state.foundComma) {
                    state.done = true;
                }

                state.foundCR = false;
                state.foundLF = false;
                state.foundCRLF = false;
                state.foundComma = false;
                state.start = buffer.getReaderIndex();
            }
        }

        return state;
    }

    public static HeaderValueState readHeaderValues(final Buffer headerName, final Buffer buffer) throws IOException {
        final HeaderValueState state = new HeaderValueState(buffer.getReaderIndex());
        return readHeaderValues(state, headerName, buffer);
    }

    /**
     * Get the next header, which may actually be returning multiple if there
     * are multiple headers on the same line.
     *
     * @param buffer
     * @return an array where the first element is the name of the buffer and
     *         the second element is the value of the buffer
     * @throws SipParseException
     */
    public static SipHeader nextHeader(final Buffer buffer) throws SipParseException {

        try {

            final int startIndex = buffer.getReaderIndex();
            int nameIndex = 0;
            while (buffer.hasReadableBytes() && nameIndex == 0) {
                if (isNext(buffer, SP) || isNext(buffer, HTAB) || isNext(buffer, COLON)) {
                    nameIndex = buffer.getReaderIndex();
                } else {
                    buffer.readByte();
                }
            }

            // Bad header! No HCOLON found! (or beginning thereof anyway)
            if (nameIndex == 0) {
                // probably ran out of bytes to read so lets just return null
                return null;
            }

            final Buffer name = buffer.slice(startIndex, nameIndex);

            expectHCOLON(buffer);

            // Note, we are framing headers from a sip request or response which
            // is why we safely can assume that there will ALWAYS be a CRLF
            // after each line
            Buffer valueBuffer = buffer.readLine();

            // Folded lines are rare so try and avoid the bulk of
            // the work if possible.
            if (isNext(buffer, SP) || isNext(buffer, HTAB)) {
                List<Buffer> foldedLines = null;
                boolean done = false;

                while (!done) {
                    if (isNext(buffer, SP) || isNext(buffer, HTAB)) {
                        consumeWS(buffer);
                        if (foldedLines == null) {
                            foldedLines = new ArrayList<Buffer>(2);
                        }
                        foldedLines.add(buffer.readLine());
                    } else {
                        done = true;
                    }
                }

                if (foldedLines != null) {
                    // even though stupid, folded lines are not that common
                    // so optimize if this ever becomes a problem.
                    String stupid = valueBuffer.toString();
                    for (final Buffer line : foldedLines) {
                        stupid += " " + line.toString();
                    }
                    valueBuffer = Buffers.wrap(stupid.getBytes(StandardCharsets.UTF_8));
                    consumeWS(valueBuffer);
                }
            }

            return new SipHeaderImpl(name, valueBuffer);
        } catch (final IOException e) {
            throw new SipParseException(buffer.getReaderIndex(), UNABLE_TO_READ_FROM_STREAM, e);
        }
    }

    /**
     * Frame the supplied buffer into a {@link SipMessage}. No deep analysis of the message will be
     * performed by this framer so there is no guarantee that this {@link SipMessage} is actually a
     * well formed message.
     *
     * @param buffer
     * @return the framed {@link SipMessage}
     */
    public static SipMessage frame(final Buffer buffer) throws IOException {

        if (!couldBeSipMessage(buffer)) {
            throw new SipParseException(0, "Cannot be a SIP message because is doesnt start with \"SIP\" "
                                           + "(for responses) or a method (for requests)");
        }

        final int startIndex = buffer.getReaderIndex();

        final SipInitialLine initialLine = SipInitialLine.parse(buffer.readLine());

        // Move along as long as we actually can consume an header and
        Buffer headerName;
        SipHeader header;
        SipHeader toHeader = null;
        SipHeader fromHeader = null;
        SipHeader cSeqHeader = null;
        SipHeader callIdHeader = null;
        SipHeader maxForwardsHeader = null;
        SipHeader viaHeader = null;
        SipHeader routeHeader = null;
        SipHeader recordRouteHeader = null;
        SipHeader contactHeader = null;
        final Map<String, List<SipHeader>> headers = new HashMap<>();
        int contentLength = 0;

        while (consumeCRLF(buffer) != 2 && (headerName = SipParser.nextHeaderName(buffer)) != null) {
            final List<Buffer> values = readHeaderValues(headerName, buffer).values;
            for (final Buffer value : values) {
                header = new SipHeaderImpl(headerName, value);
                // The headers that are most commonly used will be fully
                // parsed just because no stack can really function without
                // looking into these headers.
                if (header.isContentLengthHeader()) {
                    final ContentLengthHeader l = header.ensure().toContentLengthHeader();
                    contentLength = l.getContentLength();
                    header = l;
                } else if (contactHeader == null && header.isContactHeader()) {
                    header = header.ensure();
                    contactHeader = header;
                } else if (cSeqHeader == null && header.isCSeqHeader()) {
                    header = header.ensure();
                    cSeqHeader = header;
                } else if ( maxForwardsHeader == null && header.isMaxForwardsHeader()) {
                    header = header.ensure();
                    maxForwardsHeader = header;
                } else if (fromHeader == null && header.isFromHeader()) {
                    header = header.ensure();
                    fromHeader = header;
                } else if (toHeader == null && header.isToHeader()) {
                    header = header.ensure();
                    toHeader = header;
                } else if (viaHeader == null && header.isViaHeader()) {
                    header = header.ensure();
                    viaHeader = header;
                } else if (callIdHeader == null && header.isCallIdHeader()) {
                    header = header.ensure();
                    callIdHeader = header;
                } else if (routeHeader == null && header.isRouteHeader()) {
                    header = header.ensure();
                    routeHeader = header;
                } else if (recordRouteHeader == null && header.isRecordRouteHeader()) {
                    header = header.ensure();
                    recordRouteHeader = header;
                }

                headers.computeIfAbsent(headerName.toString(), k -> new ArrayList<>(4)).add(header);
            }
        }

        // final Buffer headers = buffer.slice(startHeaders, buffer.getReaderIndex());
        Buffer payload = null;
        if (contentLength > 0 && buffer.hasReadableBytes()) {
            payload = buffer.readBytes(Math.min(contentLength, buffer.getReadableBytes()));
        } else {
            payload = Buffers.EMPTY_BUFFER;
        }

        // slice out the entire message from this buffer since
        // everything is immutable when this message potentially
        // is written to socket again, there is really nothing to do
        // other than just write these bytes out.
        final Buffer msg = buffer.slice(startIndex, buffer.getReaderIndex());

        if (initialLine.isRequestLine()) {
            return new ImmutableSipRequest(msg,
                                           initialLine.toRequestLine(),
                                           headers,
                                           toHeader,
                                           fromHeader,
                                           cSeqHeader,
                                           callIdHeader,
                                           maxForwardsHeader,
                                           viaHeader,
                                           routeHeader,
                                           recordRouteHeader,
                                           contactHeader,
                                           payload);
        } else {
            return new ImmutableSipResponse(msg,
                                            initialLine.toResponseLine(),
                                            headers,
                                            toHeader,
                                            fromHeader,
                                            cSeqHeader,
                                            callIdHeader,
                                            maxForwardsHeader,
                                            viaHeader,
                                            routeHeader,
                                            recordRouteHeader,
                                            contactHeader,
                                            payload);
        }
    }

    /**
     * Helper function that checks whether or not the data could be a SIP message. It is a very
     * basic check but if it doesn't go through it definitely is not a SIP message.
     *
     * @param data
     * @return
     */
    public static boolean couldBeSipMessage(final Buffer data) throws IOException {
        final byte a = data.getByte(0);
        final byte b = data.getByte(1);
        final byte c = data.getByte(2);
        return couldBeSipMessage(a, b, c);
    }

    public static boolean couldBeSipMessage(final byte a, final byte b, final byte c) throws IOException {
        return a == 'S' && b == 'I' && c == 'P' || // response
               a == 'I' && b == 'N' && c == 'V' || // INVITE
               a == 'A' && b == 'C' && c == 'K' || // ACK
               a == 'B' && b == 'Y' && c == 'E' || // BYE
               a == 'O' && b == 'P' && c == 'T' || // OPTIONS
               a == 'C' && b == 'A' && c == 'N' || // CANCEL
               a == 'M' && b == 'E' && c == 'S' || // MESSAGE
               a == 'R' && b == 'E' && c == 'G' || // REGISTER
               a == 'I' && b == 'N' && c == 'F' || // INFO
               a == 'P' && b == 'R' && c == 'A' || // PRACK
               a == 'S' && b == 'U' && c == 'B' || // SUBSCRIBE
               a == 'N' && b == 'O' && c == 'T' || // NOTIFY
               a == 'U' && b == 'P' && c == 'D' || // UPDATE
               a == 'R' && b == 'E' && c == 'F' || // REFER
               a == 'P' && b == 'U' && c == 'B'; // PUBLISH

    }

}
