// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "quiche/http2/test_tools/http2_frame_builder.h"

#include "absl/strings/escaping.h"
#include "quiche/common/platform/api/quiche_test.h"

namespace http2 {
namespace test {
namespace {

const char kHighBitSetMsg[] = "High-bit of uint32_t should be clear";

TEST(Http2FrameBuilderTest, Constructors) {
  {
    Http2FrameBuilder fb;
    EXPECT_EQ(0u, fb.size());
  }
  {
    Http2FrameBuilder fb(Http2FrameType::DATA, 0, 123);
    EXPECT_EQ(9u, fb.size());

    const std::string kData = absl::HexStringToBytes(
        "000000"      // Payload length: 0 (unset)
        "00"          // Frame type: DATA
        "00"          // Flags: none
        "0000007b");  // Stream ID: 123
    EXPECT_EQ(kData, fb.buffer());
  }
  {
    Http2FrameHeader header;
    header.payload_length = (1 << 24) - 1;
    header.type = Http2FrameType::HEADERS;
    header.flags = Http2FrameFlag::END_HEADERS;
    header.stream_id = StreamIdMask();
    Http2FrameBuilder fb(header);
    EXPECT_EQ(9u, fb.size());

    const std::string kData = absl::HexStringToBytes(
        "ffffff"      // Payload length: 2^24 - 1 (max uint24)
        "01"          // Frame type: HEADER
        "04"          // Flags: END_HEADERS
        "7fffffff");  // Stream ID: stream id mask
    EXPECT_EQ(kData, fb.buffer());
  }
}

TEST(Http2FrameBuilderTest, SetPayloadLength) {
  Http2FrameBuilder fb(Http2FrameType::DATA, PADDED, 20000);
  EXPECT_EQ(9u, fb.size());

  fb.AppendUInt8(50);  // Trailing payload length
  EXPECT_EQ(10u, fb.size());

  fb.Append("ten bytes.");
  EXPECT_EQ(20u, fb.size());

  fb.AppendZeroes(50);
  EXPECT_EQ(70u, fb.size());

  fb.SetPayloadLength();
  EXPECT_EQ(70u, fb.size());

  const std::string kData = absl::HexStringToBytes(
      "00003d"                  // Payload length: 61
      "00"                      // Frame type: DATA
      "08"                      // Flags: PADDED
      "00004e20"                // Stream ID: 20000
      "32"                      // Padding Length: 50
      "74656e2062797465732e"    // "ten bytes."
      "00000000000000000000"    // Padding bytes
      "00000000000000000000"    // Padding bytes
      "00000000000000000000"    // Padding bytes
      "00000000000000000000"    // Padding bytes
      "00000000000000000000");  // Padding bytes
  EXPECT_EQ(kData, fb.buffer());
}

TEST(Http2FrameBuilderTest, Settings) {
  Http2FrameBuilder fb(Http2FrameType::SETTINGS, 0, 0);
  Http2SettingFields sf;

  sf.parameter = Http2SettingsParameter::HEADER_TABLE_SIZE;
  sf.value = 1 << 12;
  fb.Append(sf);

  sf.parameter = Http2SettingsParameter::ENABLE_PUSH;
  sf.value = 0;
  fb.Append(sf);

  sf.parameter = Http2SettingsParameter::MAX_CONCURRENT_STREAMS;
  sf.value = ~0;
  fb.Append(sf);

  sf.parameter = Http2SettingsParameter::INITIAL_WINDOW_SIZE;
  sf.value = 1 << 16;
  fb.Append(sf);

  sf.parameter = Http2SettingsParameter::MAX_FRAME_SIZE;
  sf.value = 1 << 14;
  fb.Append(sf);

  sf.parameter = Http2SettingsParameter::MAX_HEADER_LIST_SIZE;
  sf.value = 1 << 10;
  fb.Append(sf);

  size_t payload_size = 6 * Http2SettingFields::EncodedSize();
  EXPECT_EQ(Http2FrameHeader::EncodedSize() + payload_size, fb.size());

  fb.SetPayloadLength(payload_size);

  const std::string kData = absl::HexStringToBytes(
      "000024"      // Payload length: 36
      "04"          // Frame type: SETTINGS
      "00"          // Flags: none
      "00000000"    // Stream ID: 0
      "0001"        // HEADER_TABLE_SIZE
      "00001000"    // 4096
      "0002"        // ENABLE_PUSH
      "00000000"    // 0
      "0003"        // MAX_CONCURRENT_STREAMS
      "ffffffff"    // 0xffffffff (max uint32)
      "0004"        // INITIAL_WINDOW_SIZE
      "00010000"    // 4096
      "0005"        // MAX_FRAME_SIZE
      "00004000"    // 4096
      "0006"        // MAX_HEADER_LIST_SIZE
      "00000400");  // 1024
  EXPECT_EQ(kData, fb.buffer());
}

TEST(Http2FrameBuilderTest, EnhanceYourCalm) {
  const std::string kData = absl::HexStringToBytes("0000000b");
  {
    Http2FrameBuilder fb;
    fb.Append(Http2ErrorCode::ENHANCE_YOUR_CALM);
    EXPECT_EQ(kData, fb.buffer());
  }
  {
    Http2FrameBuilder fb;
    Http2RstStreamFields rsp;
    rsp.error_code = Http2ErrorCode::ENHANCE_YOUR_CALM;
    fb.Append(rsp);
    EXPECT_EQ(kData, fb.buffer());
  }
}

TEST(Http2FrameBuilderTest, PushPromise) {
  const std::string kData = absl::HexStringToBytes("7fffffff");
  {
    Http2FrameBuilder fb;
    fb.Append(Http2PushPromiseFields{0x7fffffff});
    EXPECT_EQ(kData, fb.buffer());
  }
  {
    Http2FrameBuilder fb;
    // Will generate an error if the high-bit of the stream id is set.
    EXPECT_NONFATAL_FAILURE(fb.Append(Http2PushPromiseFields{0xffffffff}),
                            kHighBitSetMsg);
    EXPECT_EQ(kData, fb.buffer());
  }
}

TEST(Http2FrameBuilderTest, Ping) {
  Http2FrameBuilder fb;
  Http2PingFields ping{"8 bytes"};
  fb.Append(ping);

  const absl::string_view kData{"8 bytes\0", 8};
  EXPECT_EQ(kData.size(), Http2PingFields::EncodedSize());
  EXPECT_EQ(kData, fb.buffer());
}

TEST(Http2FrameBuilderTest, GoAway) {
  const std::string kData = absl::HexStringToBytes(
      "12345678"    // Last Stream Id
      "00000001");  // Error code
  EXPECT_EQ(kData.size(), Http2GoAwayFields::EncodedSize());
  {
    Http2FrameBuilder fb;
    Http2GoAwayFields ga(0x12345678, Http2ErrorCode::PROTOCOL_ERROR);
    fb.Append(ga);
    EXPECT_EQ(kData, fb.buffer());
  }
  {
    Http2FrameBuilder fb;
    // Will generate a test failure if the high-bit of the stream id is set.
    Http2GoAwayFields ga(0x92345678, Http2ErrorCode::PROTOCOL_ERROR);
    EXPECT_NONFATAL_FAILURE(fb.Append(ga), kHighBitSetMsg);
    EXPECT_EQ(kData, fb.buffer());
  }
}

TEST(Http2FrameBuilderTest, WindowUpdate) {
  Http2FrameBuilder fb;
  fb.Append(Http2WindowUpdateFields{123456});

  // Will generate a test failure if the high-bit of the increment is set.
  EXPECT_NONFATAL_FAILURE(fb.Append(Http2WindowUpdateFields{0x80000001}),
                          kHighBitSetMsg);

  // Will generate a test failure if the increment is zero.
  EXPECT_NONFATAL_FAILURE(fb.Append(Http2WindowUpdateFields{0}), "non-zero");

  const std::string kData = absl::HexStringToBytes(
      "0001e240"    // Valid Window Size Increment
      "00000001"    // High-bit cleared
      "00000000");  // Invalid Window Size Increment
  EXPECT_EQ(kData.size(), 3 * Http2WindowUpdateFields::EncodedSize());
  EXPECT_EQ(kData, fb.buffer());
}

TEST(Http2FrameBuilderTest, AltSvc) {
  Http2FrameBuilder fb;
  fb.Append(Http2AltSvcFields{99});
  fb.Append(Http2AltSvcFields{0});  // No optional origin
  const std::string kData = absl::HexStringToBytes(
      "0063"    // Has origin.
      "0000");  // Doesn't have origin.
  EXPECT_EQ(kData.size(), 2 * Http2AltSvcFields::EncodedSize());
  EXPECT_EQ(kData, fb.buffer());
}

}  // namespace
}  // namespace test
}  // namespace http2
