public static int indexOf(ByteBuf needle, ByteBuf haystack) {
        if (haystack == null || needle == null) {
            return -1;
        }

        if (needle.readableBytes() > haystack.readableBytes()) {
            return -1;
        }

        int n = haystack.readableBytes();
        int m = needle.readableBytes();
        if (m == 0) {
            return 0;
        }

        // When the needle has only one byte that can be read,
        // the ByteBuf.indexOf() can be used
        if (m == 1) {
            return haystack.indexOf(haystack.readerIndex(), haystack.writerIndex(),
                          needle.getByte(needle.readerIndex()));
        }

        int i;
        int j = 0;
        int aStartIndex = needle.readerIndex();
        int bStartIndex = haystack.readerIndex();
        long suffixes =  maxSuf(needle, m, aStartIndex, true);
        long prefixes = maxSuf(needle, m, aStartIndex, false);
        int ell = Math.max((int) (suffixes >> 32), (int) (prefixes >> 32));
        int per = Math.max((int) suffixes, (int) prefixes);
        int memory;
        int length = Math.min(m - per, ell + 1);

        if (equals(needle, aStartIndex, needle, aStartIndex + per,  length)) {
            memory = -1;
            while (j <= n - m) {
                i = Math.max(ell, memory) + 1;
                while (i < m && needle.getByte(i + aStartIndex) == haystack.getByte(i + j + bStartIndex)) {
                    ++i;
                }
                if (i > n) {
                    return -1;
                }
                if (i >= m) {
                    i = ell;
                    while (i > memory && needle.getByte(i + aStartIndex) == haystack.getByte(i + j + bStartIndex)) {
                        --i;
                    }
                    if (i <= memory) {
                        return j + bStartIndex;
                    }
                    j += per;
                    memory = m - per - 1;
                } else {
                    j += i - ell;
                    memory = -1;
                }
            }
        } else {
            per = Math.max(ell + 1, m - ell - 1) + 1;
            while (j <= n - m) {
                i = ell + 1;
                while (i < m && needle.getByte(i + aStartIndex) == haystack.getByte(i + j + bStartIndex)) {
                    ++i;
                }
                if (i > n) {
                    return -1;
                }
                if (i >= m) {
                    i = ell;
                    while (i >= 0 && needle.getByte(i + aStartIndex) == haystack.getByte(i + j + bStartIndex)) {
                        --i;
                    }
                    if (i < 0) {
                        return j + bStartIndex;
                    }
                    j += per;
                } else {
                    j += i - ell;
                }
            }
        }
        return -1;
    }
@Test
    public void testIndexOf() {
        ByteBuf haystack = Unpooled.copiedBuffer("abc123", CharsetUtil.UTF_8);
        assertEquals(0, ByteBufUtil.indexOf(Unpooled.copiedBuffer("a", CharsetUtil.UTF_8), haystack));
        assertEquals(1, ByteBufUtil.indexOf(Unpooled.copiedBuffer("bc".getBytes(CharsetUtil.UTF_8)), haystack));
        assertEquals(2, ByteBufUtil.indexOf(Unpooled.copiedBuffer("c".getBytes(CharsetUtil.UTF_8)), haystack));
        assertEquals(0, ByteBufUtil.indexOf(Unpooled.copiedBuffer("abc12".getBytes(CharsetUtil.UTF_8)), haystack));
        assertEquals(-1, ByteBufUtil.indexOf(Unpooled.copiedBuffer("abcdef".getBytes(CharsetUtil.UTF_8)), haystack));
        assertEquals(-1, ByteBufUtil.indexOf(Unpooled.copiedBuffer("abc12x".getBytes(CharsetUtil.UTF_8)), haystack));
        assertEquals(-1, ByteBufUtil.indexOf(Unpooled.copiedBuffer("abc123def".getBytes(CharsetUtil.UTF_8)), haystack));

        final ByteBuf needle = Unpooled.copiedBuffer("abc12", CharsetUtil.UTF_8);
        haystack.readerIndex(1);
        needle.readerIndex(1);
        assertEquals(1, ByteBufUtil.indexOf(needle, haystack));
        haystack.readerIndex(2);
        needle.readerIndex(3);
        assertEquals(3, ByteBufUtil.indexOf(needle, haystack));
        haystack.readerIndex(1);
        needle.readerIndex(2);
        assertEquals(2, ByteBufUtil.indexOf(needle, haystack));
        haystack.release();

        haystack = new WrappedByteBuf(Unpooled.copiedBuffer("abc123", CharsetUtil.UTF_8));
        assertEquals(0, ByteBufUtil.indexOf(Unpooled.copiedBuffer("a", CharsetUtil.UTF_8), haystack));
        assertEquals(1, ByteBufUtil.indexOf(Unpooled.copiedBuffer("bc".getBytes(CharsetUtil.UTF_8)), haystack));
        assertEquals(-1, ByteBufUtil.indexOf(Unpooled.copiedBuffer("abcdef".getBytes(CharsetUtil.UTF_8)), haystack));
        haystack.release();

        haystack = Unpooled.copiedBuffer("123aab123", CharsetUtil.UTF_8);
        assertEquals(3, ByteBufUtil.indexOf(Unpooled.copiedBuffer("aab", CharsetUtil.UTF_8), haystack));
        haystack.release();
        needle.release();
    }