#include "httpfsm.h"
#include "library-htfetch_ut_hreflang_in.h"
#include "library-htfetch_ut_hreflang_out.h"

#include <util/generic/ptr.h>
#include <library/cpp/charset/doccodes.h>
#include <library/cpp/testing/unittest/registar.h>

class THttpHeaderParserTestSuite: public TTestBase {
    UNIT_TEST_SUITE(THttpHeaderParserTestSuite);
    UNIT_TEST(TestRequestHeader);
    UNIT_TEST(TestSplitRequestHeader);
    UNIT_TEST(TestTrailingData);
    UNIT_TEST(TestProxyRequestHeader);
    UNIT_TEST(TestIncorrectRequestHeader);
    UNIT_TEST(TestLastModified);
    UNIT_TEST(TestLastModifiedCorrupted);
    UNIT_TEST(TestResponseHeaderOnRequest);
    UNIT_TEST(TestRequestHeaderOnResponse);
    UNIT_TEST(TestXRobotsTagUnknownTags);
    UNIT_TEST(TestXRobotsTagMyBot);
    UNIT_TEST(TestXRobotsTagOtherBot);
    UNIT_TEST(TestXRobotsTagUnavailableAfterAware);
    UNIT_TEST(TestXRobotsTagUnavailableAfterWorks);
    UNIT_TEST(TestXRobotsTagOverridePriority);
    UNIT_TEST(TestXRobotsTagDoesNotBreakCharset);
    UNIT_TEST(TestXRobotsTagAllowsMultiline);
    UNIT_TEST(TestRelCanonical);
    UNIT_TEST(TestHreflang);
    UNIT_TEST(TestHreflangOnLongInput);
    UNIT_TEST(TestMimeType);
    UNIT_TEST(TestRepeatedContentEncoding);
    UNIT_TEST_SUITE_END();

private:
    THolder<THttpHeaderParser> httpHeaderParser;

private:
    void TestStart();
    void TestFinish();

public:
    void TestRequestHeader();
    void TestSplitRequestHeader();
    void TestTrailingData();
    void TestProxyRequestHeader();
    void TestIncorrectRequestHeader();
    void TestLastModified();
    void TestLastModifiedCorrupted();
    void TestResponseHeaderOnRequest();
    void TestRequestHeaderOnResponse();
    void TestXRobotsTagUnknownTags();
    void TestXRobotsTagMyBot();
    void TestXRobotsTagOtherBot();
    void TestXRobotsTagUnavailableAfterAware();
    void TestXRobotsTagUnavailableAfterWorks();
    void TestXRobotsTagOverridePriority();
    void TestXRobotsTagDoesNotBreakCharset();
    void TestXRobotsTagAllowsMultiline();
    void TestRelCanonical();
    void TestHreflang();
    void TestHreflangOnLongInput();
    void TestMimeType();
    void TestRepeatedContentEncoding();
};

void THttpHeaderParserTestSuite::TestStart() {
    httpHeaderParser.Reset(new THttpHeaderParser());
}

void THttpHeaderParserTestSuite::TestFinish() {
    httpHeaderParser.Reset();
}

void THttpHeaderParserTestSuite::TestRequestHeader() {
    TestStart();
    THttpRequestHeader httpRequestHeader;
    httpHeaderParser->Init(&httpRequestHeader);
    const char* request = "GET /search?q=hi HTTP/1.1\r\n"
                          "Host: www.google.ru:8080\r\n\r\n";
    i32 result = httpHeaderParser->Execute(request, strlen(request));
    UNIT_ASSERT_EQUAL(result, 2);
    UNIT_ASSERT_EQUAL(httpRequestHeader.http_method, HTTP_METHOD_GET);
    UNIT_ASSERT_EQUAL(strcmp(httpRequestHeader.host, "www.google.ru:8080"), 0);
    UNIT_ASSERT_EQUAL(httpRequestHeader.request_uri, "/search?q=hi");
    UNIT_ASSERT_EQUAL(httpRequestHeader.GetUrl(), "http://www.google.ru:8080/search?q=hi");
    UNIT_ASSERT_EQUAL(httpHeaderParser->lastchar - request + 1,
                      (i32)strlen(request));
    UNIT_ASSERT_EQUAL(httpRequestHeader.x_yandex_response_timeout,
                      DEFAULT_RESPONSE_TIMEOUT);
    UNIT_ASSERT_EQUAL(httpRequestHeader.x_yandex_request_priority,
                      DEFAULT_REQUEST_PRIORITY);
    UNIT_ASSERT_EQUAL(strcmp(httpRequestHeader.x_yandex_sourcename, ""), 0);
    UNIT_ASSERT_EQUAL(strcmp(httpRequestHeader.x_yandex_requesttype, ""), 0);
    UNIT_ASSERT_EQUAL(strcmp(httpRequestHeader.x_yandex_fetchoptions, ""), 0);
    TestFinish();
    UNIT_ASSERT_EQUAL(httpRequestHeader.max_age, DEFAULT_MAX_AGE);
}

void THttpHeaderParserTestSuite::TestSplitRequestHeader() {
    TestStart();
    const char* request =
        "GET /search?q=hi HTTP/1.1\r\n"
        "Host:  www.google.ru:8080 \r\n"
        "\r\n";
    const size_t rlen = strlen(request);

    for (size_t n1 = 0; n1 < rlen; n1++) {
        for (size_t n2 = n1; n2 < rlen; n2++) {
            TString s1{request, 0, n1};
            TString s2{request, n1, n2 - n1};
            TString s3{request, n2, rlen - n2};
            UNIT_ASSERT_EQUAL(s1 + s2 + s3, request);

            THttpRequestHeader httpRequestHeader;
            UNIT_ASSERT(0 == httpHeaderParser->Init(&httpRequestHeader));
            i32 result = httpHeaderParser->Execute(s1);
            UNIT_ASSERT_EQUAL(result, 1);
            result = httpHeaderParser->Execute(s2);
            UNIT_ASSERT_EQUAL(result, 1);
            result = httpHeaderParser->Execute(s3);
            UNIT_ASSERT_EQUAL(result, 2);

            UNIT_ASSERT_EQUAL(httpRequestHeader.http_method, HTTP_METHOD_GET);
            UNIT_ASSERT_EQUAL(strcmp(httpRequestHeader.host, "www.google.ru:8080"), 0);
            UNIT_ASSERT_EQUAL(httpRequestHeader.request_uri, "/search?q=hi");
        }
    }

    TestFinish();
}

void THttpHeaderParserTestSuite::TestTrailingData() {
    TestStart();
    THttpRequestHeader httpRequestHeader;
    UNIT_ASSERT(0 == httpHeaderParser->Init(&httpRequestHeader));
    const char* request =
        "GET /search?q=hi HTTP/1.1\r\n"
        "Host: www.google.ru:8080\r\n"
        "\r\n"
        "high.ru";
    i32 result = httpHeaderParser->Execute(request, strlen(request));
    UNIT_ASSERT_EQUAL(result, 2);
    UNIT_ASSERT_EQUAL(httpRequestHeader.http_method, HTTP_METHOD_GET);
    UNIT_ASSERT_EQUAL(strcmp(httpRequestHeader.host, "www.google.ru:8080"), 0);
    UNIT_ASSERT_EQUAL(httpRequestHeader.request_uri, "/search?q=hi");
    UNIT_ASSERT_EQUAL(TString(httpHeaderParser->lastchar + 1), "high.ru");
    UNIT_ASSERT_EQUAL(httpRequestHeader.http_minor, 1);
    UNIT_ASSERT_EQUAL(httpRequestHeader.transfer_chunked, -1);
    UNIT_ASSERT_EQUAL(httpRequestHeader.content_length, -1);
    UNIT_ASSERT_EQUAL(httpRequestHeader.connection_closed, -1);
    TestFinish();
}

void THttpHeaderParserTestSuite::TestProxyRequestHeader() {
    TestStart();
    THttpRequestHeader httpRequestHeader;
    httpHeaderParser->Init(&httpRequestHeader);
    const char* request =
        "GET http://www.google.ru:8080/search?q=hi HTTP/1.1\r\n"
        "X-Yandex-Response-Timeout: 1000\r\n"
        "X-Yandex-Request-Priority: 2\r\n"
        "X-Yandex-Sourcename: orange\r\n"
        "X-Yandex-Requesttype: userproxy\r\n"
        "X-Yandex-FetchOptions: d;c\r\n"
        "Cache-control: max-age=100\r\n"
        "If-Modified-Since: Sat, 29 Oct 1994 19:43:31 GMT\r\n"
        "User-Agent: Yandex/1.01.001 (compatible; Win16; I)\r\n"
        "From: webadmin@yandex.ru\r\n\r\n";
    i32 result = httpHeaderParser->Execute(request, strlen(request));
    UNIT_ASSERT_EQUAL(result, 2);
    UNIT_ASSERT_EQUAL(httpRequestHeader.http_method, HTTP_METHOD_GET);
    UNIT_ASSERT_EQUAL(httpRequestHeader.x_yandex_response_timeout, 1000);
    UNIT_ASSERT_EQUAL(httpRequestHeader.x_yandex_request_priority, 2);
    UNIT_ASSERT_EQUAL(strcmp(httpRequestHeader.x_yandex_sourcename, "orange"), 0);
    UNIT_ASSERT_EQUAL(strcmp(httpRequestHeader.x_yandex_requesttype, "userproxy"), 0);
    UNIT_ASSERT_EQUAL(strcmp(httpRequestHeader.x_yandex_fetchoptions, "d;c"), 0);
    UNIT_ASSERT_EQUAL(httpRequestHeader.max_age, 100);
    UNIT_ASSERT_VALUES_EQUAL(httpRequestHeader.if_modified_since,
                             TInstant::ParseIso8601Deprecated("1994-10-29 19:43:31Z").TimeT());
    UNIT_ASSERT_EQUAL(httpRequestHeader.request_uri,
                      "http://www.google.ru:8080/search?q=hi");
    UNIT_ASSERT(httpRequestHeader.GetUrl() ==
                "http://www.google.ru:8080/search?q=hi");
    UNIT_ASSERT_EQUAL(strcmp(httpRequestHeader.host, ""), 0);
    UNIT_ASSERT_EQUAL(strcmp(httpRequestHeader.from, "webadmin@yandex.ru"), 0);
    UNIT_ASSERT_EQUAL(strcmp(httpRequestHeader.user_agent,
                             "Yandex/1.01.001 (compatible; Win16; I)"),
                      0);
    UNIT_ASSERT_EQUAL(httpHeaderParser->lastchar - request + 1,
                      (i32)strlen(request));
    TestFinish();
}

void THttpHeaderParserTestSuite::TestIncorrectRequestHeader() {
    TestStart();
    THttpRequestHeader httpRequestHeader;
    httpHeaderParser->Init(&httpRequestHeader);
    const char* request = "GET /search?q=hi HTP/1.1\r\n"
                          "Host: www.google.ru:8080\r\n\r\n";
    i32 result = httpHeaderParser->Execute(request, strlen(request));
    UNIT_ASSERT(result != 2);
    TestFinish();
}

void THttpHeaderParserTestSuite::TestLastModified() {
    TestStart();
    THttpHeader h;
    UNIT_ASSERT(0 == httpHeaderParser->Init(&h));
    const char* headers =
        "HTTP/1.1 200 OK\r\n"
        "Content-Type: text/html\r\n"
        "Last-Modified: Thu, 13 Aug 2009 14:27:08 GMT\r\n\r\n";
    UNIT_ASSERT(2 == httpHeaderParser->Execute(headers, strlen(headers)));
    UNIT_ASSERT_VALUES_EQUAL(
        TInstant::ParseIso8601Deprecated("2009-08-13 14:27:08Z").TimeT(),
        h.http_time);
    TestFinish();
}

void THttpHeaderParserTestSuite::TestLastModifiedCorrupted() {
    TestStart();
    THttpHeader h;
    UNIT_ASSERT(0 == httpHeaderParser->Init(&h));
    const char* headers =
        "HTTP/1.1 200 OK\r\n"
        "Content-Type: text/html\r\n"
        "Last-Modified: Thu, 13 Aug 2009 14:\r\n\r\n";
    UNIT_ASSERT(2 == httpHeaderParser->Execute(headers, strlen(headers)));
    UNIT_ASSERT(h.http_time < 0); // XXX: don't understand what is the proper value
    TestFinish();
}

void THttpHeaderParserTestSuite::TestXRobotsTagUnknownTags() {
    TestStart();
    THttpHeader httpHeader;
    httpHeaderParser->Init(&httpHeader);
    const char* headers =
        "HTTP/1.1 200 OK\r\n"
        "Content-Type: text/html\r\n"
        "x-robots-tag: asdfasdf asdf asdf,,, , noindex,noodpXXX , NOFOLLOW ,noodpnofollow\r\n\r\n";
    i32 result = httpHeaderParser->Execute(headers, strlen(headers));
    UNIT_ASSERT_EQUAL(result, 2);
    UNIT_ASSERT_EQUAL(httpHeader.x_robots_tag, 3);
    UNIT_ASSERT_EQUAL(httpHeader.x_robots_state, "00xxx");
    TestFinish();
}

void THttpHeaderParserTestSuite::TestXRobotsTagMyBot() {
    TestStart();
    THttpHeader httpHeader;
    httpHeaderParser->Init(&httpHeader);
    const char* headers =
        "HTTP/1.1 200 OK\r\n"
        "Content-Type: text/html\r\n"
        "x-robots-tag: yandex: noindex, nofollow\r\n"
        "x-robots-tag: yandexbot: noarchive, noodp\r\n\r\n";
    i32 result = httpHeaderParser->Execute(headers, strlen(headers));
    UNIT_ASSERT_EQUAL(result, 2);
    UNIT_ASSERT_EQUAL(httpHeader.x_robots_tag, 15);
    UNIT_ASSERT_EQUAL(httpHeader.x_robots_state, "0000x");
    TestFinish();
}

void THttpHeaderParserTestSuite::TestXRobotsTagOtherBot() {
    TestStart();
    THttpHeader httpHeader;
    httpHeaderParser->Init(&httpHeader);
    const char* headers =
        "HTTP/1.1 200 OK\r\n"
        "Content-Type: text/html\r\n"
        "x-robots-tag: google: noindex, nofollow\r\n"
        "x-robots-tag: googlebot: noarchive, noodp\r\n"
        "x-robots-tag: !still(-other) bot_: foo, noyaca\r\n\r\n";
    i32 result = httpHeaderParser->Execute(headers, strlen(headers));
    UNIT_ASSERT_EQUAL(result, 2);
    UNIT_ASSERT_EQUAL(httpHeader.x_robots_tag, 0);
    UNIT_ASSERT_EQUAL(httpHeader.x_robots_state, "xxxxx");
    TestFinish();
}

void THttpHeaderParserTestSuite::TestXRobotsTagUnavailableAfterAware() {
    TestStart();
    THttpHeader httpHeader;
    httpHeaderParser->Init(&httpHeader);
    // проверяем только что unavailable_after ничего не ломает
    const char* headers =
        "HTTP/1.1 200 OK\r\n"
        "Content-Type: text/html\r\n"
        "x-robots-tag: unavailable_after: 01 Jan 2999 00:00 UTC, noindex, nofollow\r\n"
        "x-robots-tag: yandex: unavailable_after: 01 Jan 2999 00:00 UTC, noarchive, noodp\r\n\r\n";
    i32 result = httpHeaderParser->Execute(headers, strlen(headers));
    UNIT_ASSERT_EQUAL(result, 2);
    UNIT_ASSERT_EQUAL(httpHeader.x_robots_tag, 15);
    UNIT_ASSERT_EQUAL(httpHeader.x_robots_state, "0000x");
    TestFinish();
}

void THttpHeaderParserTestSuite::TestXRobotsTagUnavailableAfterWorks() {
    TestStart();
    THttpHeader httpHeader;
    httpHeaderParser->Init(&httpHeader);
    // пока не поддерживается
    const char* headers =
        "HTTP/1.1 200 OK\r\n"
        "Content-Type: text/html\r\n"
        "x-robots-tag: unavailable_after: 01 Jan 2000 00:00 UTC\r\n\r\n";
    i32 result = httpHeaderParser->Execute(headers, strlen(headers));
    UNIT_ASSERT_EQUAL(result, 2);
    //UNIT_ASSERT_EQUAL(httpHeader.x_robots_tag, 1);
    //UNIT_ASSERT_EQUAL(httpHeader.x_robots_state, "0xxxx");
    TestFinish();
}

void THttpHeaderParserTestSuite::TestXRobotsTagOverridePriority() {
    TestStart();
    THttpHeader httpHeader;
    httpHeaderParser->Init(&httpHeader);
    const char* headers =
        "HTTP/1.1 200 OK\r\n"
        "Content-Type: text/html\r\n"
        "x-robots-tag: all, none\r\n\r\n";
    i32 result = httpHeaderParser->Execute(headers, strlen(headers));
    UNIT_ASSERT_EQUAL(result, 2);
    UNIT_ASSERT_EQUAL(httpHeader.x_robots_state, "11xxx");
    UNIT_ASSERT_EQUAL(httpHeader.x_robots_tag, 3); // NOTE legacy behavior, should be 0 as `all` overrides
    TestFinish();
}

void THttpHeaderParserTestSuite::TestXRobotsTagDoesNotBreakCharset() {
    TestStart();
    THttpHeader httpHeader;
    httpHeaderParser->Init(&httpHeader);
    const char* headers =
        "HTTP/1.1 200 OK\r\n"
        "X-Robots-Tag: noarchive\r\n"
        "Content-Type: application/json; charset=utf-8\r\n\r\n";
    i32 result = httpHeaderParser->Execute(headers, strlen(headers));
    UNIT_ASSERT_EQUAL(result, 2);
    UNIT_ASSERT_EQUAL(httpHeader.mime_type, static_cast<ui8>(MIME_JSON));
    UNIT_ASSERT_EQUAL(httpHeader.charset, static_cast<ui8>(CODES_UTF8));
    TestFinish();
}

void THttpHeaderParserTestSuite::TestXRobotsTagAllowsMultiline() {
    TestStart();
    THttpHeader httpHeader;
    httpHeaderParser->Init(&httpHeader);
    const char* headers =
        "HTTP/1.1 200 OK\r\n"
        "X-Robots-Tag\r\n"
        " :\r\n"
        " unavailable_since\r\n"
        " :\r\n"
        " ,\r\n"
        " unavailable_since\r\n"
        " :\r\n"
        " 01 Jan 2000\r\n"
        " 00:00 UTC\r\n"
        " ,\r\n"
        " yandexbot\r\n"
        " :\r\n"
        " noindex\r\n"
        " ,\r\n"
        " garbage\r\n"
        " ,\r\n"
        " nofollow\r\n"
        " ,\r\n"
        " other\r\n"
        " bot\r\n"
        " :\r\n"
        " noarchive\r\n"
        " ,\r\n"
        "Content-Type: application/json; charset=utf-8\r\n\r\n";
    i32 result = httpHeaderParser->Execute(headers, strlen(headers));
    UNIT_ASSERT_EQUAL(result, 2);
    UNIT_ASSERT_EQUAL(httpHeader.x_robots_state, "00xxx");
    UNIT_ASSERT_EQUAL(httpHeader.mime_type, static_cast<ui8>(MIME_JSON));
    UNIT_ASSERT_EQUAL(httpHeader.charset, static_cast<ui8>(CODES_UTF8));
    TestFinish();
}

void THttpHeaderParserTestSuite::TestHreflang() {
    TestStart();
    THttpHeader httpHeader;
    httpHeaderParser->Init(&httpHeader);
    const char* headers =
        "HTTP/1.1 200 OK\r\n"
        "Content-Type: text/html\r\n"
        "link: <http://www.high.ru/>; rel='alternate'; hreflang='x-default'\r\n"
        "link:  <http://www.high.ru/en.html> ;rel  =  'alternate'  ;hreflang =   en_GB  \r\n"
        "link:  <http://www.high.ru/ru.html>;hreflang =   ru_RU.KOI8-r   ;rel  =  'alternate'  \r\n"
        "\r\n";
    i32 result = httpHeaderParser->Execute(headers, strlen(headers));
    UNIT_ASSERT_VALUES_EQUAL(result, 2);
    // UNIT_ASSERT_VALUES_EQUAL(strcmp(httpHeader.hreflangs, "x-default http://www.high.ru/;"), 0);
    UNIT_ASSERT_VALUES_EQUAL(httpHeader.hreflangs, "x-default http://www.high.ru/\ten_GB http://www.high.ru/en.html\tru_RU.KOI8-r http://www.high.ru/ru.html");
    TestFinish();
}

void THttpHeaderParserTestSuite::TestHreflangOnLongInput() {
    TestStart();
    THttpHeader httpHeader;
    httpHeaderParser->Init(&httpHeader);
    TStringBuf testInput(hreflang_ut_in);
    TStringBuf testOut(hreflang_ut_out);
    i32 result = httpHeaderParser->Execute(testInput.data(), testInput.size());
    UNIT_ASSERT_VALUES_EQUAL(result, 2);
    UNIT_ASSERT_VALUES_EQUAL(httpHeader.hreflangs, testOut);
    TestFinish();
}

void THttpHeaderParserTestSuite::TestRelCanonical() {
    TestStart();
    THttpHeader httpHeader;
    httpHeaderParser->Init(&httpHeader);
    const char* headers =
        "HTTP/1.1 200 OK\r\n"
        "Content-Type: text/html\r\n"
        "Link: <http://yandex.ru>; rel = \"canonical\"\r\n\r\n";
    i32 result = httpHeaderParser->Execute(headers, strlen(headers));
    UNIT_ASSERT_EQUAL(result, 2);
    UNIT_ASSERT_EQUAL(httpHeader.rel_canonical, "http://yandex.ru");
    TestFinish();
}

void THttpHeaderParserTestSuite::TestResponseHeaderOnRequest() {
    TestStart();
    THttpHeader httpHeader;
    httpHeaderParser->Init(&httpHeader);
    const char* request = "GET /search?q=hi HTP/1.1\r\n"
                          "Host: www.google.ru:8080\r\n\r\n";
    i32 result = httpHeaderParser->Execute(request, strlen(request));
    UNIT_ASSERT_EQUAL(result, -3);
    TestFinish();
}

void THttpHeaderParserTestSuite::TestRequestHeaderOnResponse() {
    TestStart();
    THttpRequestHeader httpRequestHeader;
    httpHeaderParser->Init(&httpRequestHeader);
    const char* response = "HTTP/1.1 200 OK\r\n"
                           "Content-Type: text/html\r\n"
                           "Last-Modified: Thu, 13 Aug 2009 14:\r\n\r\n";
    i32 result = httpHeaderParser->Execute(response, strlen(response));
    UNIT_ASSERT_EQUAL(result, -3);
    TestFinish();
}

void THttpHeaderParserTestSuite::TestMimeType() {
    TestStart();
    THttpHeader httpHeader;
    httpHeaderParser->Init(&httpHeader);
    const char* headers =
        "HTTP/1.1 200 OK\r\n"
        "Content-Type: application/json; charset=utf-8\r\n\r\n";
    i32 result = httpHeaderParser->Execute(headers, strlen(headers));
    UNIT_ASSERT_EQUAL(result, 2);
    UNIT_ASSERT_EQUAL(httpHeader.mime_type, static_cast<ui8>(MIME_JSON));
    UNIT_ASSERT_EQUAL(httpHeader.charset, static_cast<ui8>(CODES_UTF8));
    TestFinish();
}

void THttpHeaderParserTestSuite::TestRepeatedContentEncoding() {
    TestStart();
    THttpHeader httpHeader;
    httpHeaderParser->Init(&httpHeader);
    const char *headers =
        "HTTP/1.1 200 OK\r\n"
        "Server: nginx\r\n"
        "Date: Mon, 15 Oct 2018 10:40:44 GMT\r\n"
        "Content-Type: text/plain\r\n"
        "Transfer-Encoding: chunked\r\n"
        "Connection: keep-alive\r\n"
        "Last-Modified: Mon, 15 Oct 2018 03:48:54 GMT\r\n"
        "ETag: W/\"5bc40e26-a956d\"\r\n"
        "X-Autoru-LB: lb-03-sas.prod.vertis.yandex.net\r\n"
        "Content-Encoding: gzip\r\n"
        "Content-Encoding: gzip\r\n"
        "X-UA-Bot: 1\r\n"
        "\r\n";
    i32 result = httpHeaderParser->Execute(headers, strlen(headers));
    UNIT_ASSERT_EQUAL(result, 2);
    UNIT_ASSERT_EQUAL(httpHeader.error, 0);
    UNIT_ASSERT_EQUAL(httpHeader.compression_method, 3);
    TestFinish();
}

UNIT_TEST_SUITE_REGISTRATION(THttpHeaderParserTestSuite);

Y_UNIT_TEST_SUITE(TestHttpChunkParser) {
    static THttpChunkParser initParser() {
        THttpChunkParser parser;
        parser.Init();
        return parser;
    }

    static THttpChunkParser parseByteByByte(const TStringBuf& blob, const TVector<int>& states) {
        UNIT_ASSERT(states.size() <= blob.size());
        THttpChunkParser parser{initParser()};
        for (size_t n = 0; n < states.size(); n++) {
            const TStringBuf d{blob, n, 1};
            int code = parser.Execute(d.data(), d.size());
            Cout << TString(d).Quote() << " " << code << Endl;
            UNIT_ASSERT_EQUAL(code, states[n]);
        }
        return parser;
    }

    static THttpChunkParser parseBytesWithLastState(const TStringBuf& blob, const int last_state) {
        TVector<int> states(blob.size() - 1, 1);
        states.push_back(last_state);
        return parseByteByByte(blob, states);
    }

    Y_UNIT_TEST(TestWithoutEolHead) {
        const TStringBuf blob{
            "4\r\n"
            "____\r\n"};
        TVector<int> states{
            -1, /* 1, -1,
            1, -1,  1, -1, 1, -1 */};
        // as soon as error happens parser state should be considered
        // undefined, state is meaningless after the very first `-1`
        // moreover, testenv produces `states[1] == -1` for this input and
        // my local build produces `states[1] == 1`.
        parseByteByByte(blob, states);
    }

    Y_UNIT_TEST(TestTrivialChunk) {
        const TStringBuf blob{
            "\r\n"
            "4\r\n"};
        THttpChunkParser parser(parseBytesWithLastState(blob, 2));
        UNIT_ASSERT_EQUAL(parser.chunk_length, 4);
        UNIT_ASSERT_EQUAL(parser.cnt64, 4);
    }

    Y_UNIT_TEST(TestNegative) {
        const TStringBuf blob{
            "\r\n"
            "-1"};
        TVector<int> states{
            1, 1,
            -1,
            /* 1 */};
        parseByteByByte(blob, states);
    }

    Y_UNIT_TEST(TestLeadingZero) {
        const TStringBuf blob{
            "\r\n"
            "042\r\n"};
        THttpChunkParser parser(parseBytesWithLastState(blob, 2));
        UNIT_ASSERT_EQUAL(parser.chunk_length, 0x42);
    }

    Y_UNIT_TEST(TestIntOverflow) {
        const TStringBuf blob{
            "\r\n"
            "deadbeef"};
        THttpChunkParser parser(parseBytesWithLastState(blob, -2));
        UNIT_ASSERT_EQUAL(parser.chunk_length, 0);
        UNIT_ASSERT_EQUAL(parser.cnt64, 0xdeadbeef);
    }

    Y_UNIT_TEST(TestTrivialChunkWithTail) {
        const TStringBuf blob{
            "\r\n"
            "4\r\n"
            "_" // first byte of the chunk
        };
        TVector<int> states{
            1, 1,
            1, 1, 2,
            -1};
        parseByteByByte(blob, states);
    }

    Y_UNIT_TEST(TestLastChunk) {
        // NB: current parser does not permit whitespace before `foo`,
        // but I've never seen the feature in real-life traffic
        const TStringBuf blob{
            "\r\n"
            "000 ;foo = bar \r\n"
            "Trailer: bar\r\n"
            "\r\n"};
        THttpChunkParser parser(parseBytesWithLastState(blob, 2));
        UNIT_ASSERT_EQUAL(parser.chunk_length, 0);
    }
}