/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You 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.apache.coyote.http2;

import java.nio.ByteBuffer;

import org.junit.Assert;
import org.junit.Test;

import org.apache.tomcat.util.http.MimeHeaders;

public class TestHpack {

    @Test
    public void testEncode() throws Exception {
        MimeHeaders headers = new MimeHeaders();
        headers.setValue("header1").setString("value1");
        headers.setValue(":status").setString("200");
        headers.setValue("header2").setString("value2");
        ByteBuffer output = ByteBuffer.allocate(512);
        HpackEncoder encoder = new HpackEncoder();
        encoder.encode(headers, output);
        output.flip();
        // Size is supposed to be 33 without huffman, or 27 with it
        // TODO: use the HpackHeaderFunction to enable huffman predictably
        Assert.assertEquals(27, output.remaining());
        output.clear();
        encoder.encode(headers, output);
        output.flip();
        // Size is now 3 after using the table
        Assert.assertEquals(3, output.remaining());
    }

    @Test
    public void testDecode() throws Exception {
        MimeHeaders headers = new MimeHeaders();
        headers.setValue("header1").setString("value1");
        headers.setValue(":status").setString("200");
        headers.setValue("header2").setString("value2");
        ByteBuffer output = ByteBuffer.allocate(512);
        HpackEncoder encoder = new HpackEncoder();
        encoder.encode(headers, output);
        output.flip();
        MimeHeaders headers2 = new MimeHeaders();
        HpackDecoder decoder = new HpackDecoder();
        decoder.setHeaderEmitter(new HeadersListener(headers2));
        decoder.decode(output);
        // Redo (table is supposed to be updated)
        output.clear();
        encoder.encode(headers, output);
        output.flip();
        headers2.recycle();
        Assert.assertEquals(3, output.remaining());
        // Check that the decoder is using the table right
        decoder.decode(output);
        Assert.assertEquals("value2", headers2.getHeader("header2"));
    }

    private static class HeadersListener implements HpackDecoder.HeaderEmitter {
        private final MimeHeaders headers;

        HeadersListener(MimeHeaders headers) {
            this.headers = headers;
        }

        @Override
        public void emitHeader(String name, String value) {
            headers.setValue(name).setString(value);
        }

        @Override
        public void setHeaderException(StreamException streamException) {
            // NO-OP
        }

        @Override
        public void validateHeaders() throws StreamException {
            // NO-OP
        }
    }

    @Test
    public void testHeaderValueBug60451() throws HpackException {
        doTestHeaderValueBug60451("fooébar");
    }

    @Test
    public void testHeaderValueFullRange() {
        for (int i = 0; i < 256; i++) {
            // Skip the control characters except VTAB
            if (i == 9 || i > 31 && i < 127 || i > 127) {
                try {
                    doTestHeaderValueBug60451("foo" + Character.toString((char) i) + "bar");
                } catch (Exception e) {
                    e.printStackTrace();
                    Assert.fail(e.getMessage() + "[" + i + "]");
                }
            }
        }
    }

    @Test(expected = HpackException.class)
    public void testExcessiveStringLiteralPadding() throws Exception {
        MimeHeaders headers = new MimeHeaders();
        headers.setValue("X-test").setString("foobar");
        ByteBuffer output = ByteBuffer.allocate(512);
        HpackEncoder encoder = new HpackEncoder();
        encoder.encode(headers, output);
        // Hack the output buffer to extend the EOS marker for the header value
        // by another byte
        output.array()[7] = (byte) -122;
        output.put((byte) -1);
        output.flip();
        MimeHeaders headers2 = new MimeHeaders();
        HpackDecoder decoder = new HpackDecoder();
        decoder.setHeaderEmitter(new HeadersListener(headers2));
        decoder.decode(output);
    }


    private void doTestHeaderValueBug60451(String filename) throws HpackException {
        String headerName = "Content-Disposition";
        String headerValue = "attachment;filename=\"" + filename + "\"";
        MimeHeaders headers = new MimeHeaders();
        headers.setValue(headerName).setString(headerValue);
        ByteBuffer output = ByteBuffer.allocate(512);
        HpackEncoder encoder = new HpackEncoder();
        encoder.encode(headers, output);
        output.flip();
        MimeHeaders headers2 = new MimeHeaders();
        HpackDecoder decoder = new HpackDecoder();
        decoder.setHeaderEmitter(new HeadersListener(headers2));
        decoder.decode(output);
        Assert.assertEquals(headerValue, headers2.getHeader(headerName));
    }


    @Test
    public void testDecodeIntegerMaxValue() throws HpackException {
        ByteBuffer bb = ByteBuffer.allocate(9);
        bb.put((byte) 255);
        bb.put((byte) 254);
        bb.put((byte) 255);
        bb.put((byte) 255);
        bb.put((byte) 255);
        bb.put((byte) 7);
        bb.position(0);

        Assert.assertEquals(Integer.MAX_VALUE,Hpack.decodeInteger(bb, 1));
    }


    @Test(expected = HpackException.class)
    public void testDecodeIntegerMaxValuePlus1() throws HpackException {
        ByteBuffer bb = ByteBuffer.allocate(9);
        bb.put((byte) 255);
        bb.put((byte) 255);
        bb.put((byte) 255);
        bb.put((byte) 255);
        bb.put((byte) 255);
        bb.put((byte) 7);
        bb.position(0);

        Hpack.decodeInteger(bb, 1);
    }

    @Test(expected = HpackException.class)
    public void testDecodeIntegerOverflow() throws HpackException {
        ByteBuffer bb = ByteBuffer.allocate(9);
        bb.put((byte) 255);
        bb.put((byte) 254);
        bb.put((byte) 255);
        bb.put((byte) 255);
        bb.put((byte) 255);
        bb.put((byte) 15);
        bb.position(0);

        Hpack.decodeInteger(bb, 1);
    }

    @Test(expected = HpackException.class)
    public void testDecodeIntegerZeroValues() throws HpackException {
        ByteBuffer bb = ByteBuffer.allocate(12);
        bb.put((byte) 255);
        bb.put((byte) 128);
        bb.put((byte) 128);
        bb.put((byte) 128);
        bb.put((byte) 128);
        bb.put((byte) 128);
        bb.put((byte) 128);
        bb.put((byte) 128);
        bb.put((byte) 128);
        bb.put((byte) 128);
        bb.put((byte) 128);
        bb.put((byte) 128);
        bb.position(0);

        Hpack.decodeInteger(bb, 1);
    }
}
