/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "TestCommon.h"
#include "gtest/gtest.h"
#include "Capsule.h"
#include "CapsuleEncoder.h"
#include "CapsuleParser.h"

using namespace mozilla;
using namespace mozilla::net;

TEST(TestCapsule, UnknownCapsule)
{
  nsTArray<uint8_t> data({0x1, 0x2});
  nsTArray<uint8_t> cloned(data.Clone());
  Capsule capsule = Capsule::Unknown(0x1234, std::move(cloned));
  CapsuleEncoder encoder;
  encoder.EncodeCapsule(capsule);
  auto buffer = encoder.GetBuffer();

  RefPtr<CapsuleParserListener> listener = new CapsuleParserListener();
  UniquePtr<CapsuleParser> parser = MakeUnique<CapsuleParser>(listener);
  bool res = parser->ProcessCapsuleData(buffer.Elements(), buffer.Length());
  ASSERT_TRUE(res);

  Maybe<nsresult> error = listener->GetErrorResult();
  ASSERT_TRUE(error.isNothing());

  nsTArray<Capsule> parsed = listener->GetParsedCapsules();
  ASSERT_EQ(parsed.Length(), 1u);

  UnknownCapsule& unknown = parsed[0].GetUnknownCapsule();
  ASSERT_EQ(unknown.mData, data);

  ASSERT_TRUE(parser->IsBufferEmpty());
}

TEST(TestCapsule, CloseWebTransportSessionCapsule)
{
  nsCString reason("test");
  Capsule capsule = Capsule::CloseWebTransportSession(42, reason);
  CapsuleEncoder encoder;
  encoder.EncodeCapsule(capsule);
  auto buffer = encoder.GetBuffer();

  RefPtr<CapsuleParserListener> listener = new CapsuleParserListener();
  UniquePtr<CapsuleParser> parser = MakeUnique<CapsuleParser>(listener);
  bool res = parser->ProcessCapsuleData(buffer.Elements(), buffer.Length());
  ASSERT_TRUE(res);

  Maybe<nsresult> error = listener->GetErrorResult();
  ASSERT_TRUE(error.isNothing());

  nsTArray<Capsule> parsed = listener->GetParsedCapsules();
  ASSERT_EQ(parsed.Length(), 1u);

  CloseWebTransportSessionCapsule& parsedCapsule =
      parsed[0].GetCloseWebTransportSessionCapsule();
  ASSERT_EQ(parsedCapsule.mStatus, 42u);
  ASSERT_EQ(parsedCapsule.mReason, reason);

  ASSERT_TRUE(parser->IsBufferEmpty());
}

TEST(TestCapsule, CloseWebTransportSessionCapsuleWithReasonTooLong)
{
  nsCString reason;
  for (uint32_t i = 0; i < 1025; i++) {
    reason.AppendLiteral("1");
  }

  Capsule capsule = Capsule::CloseWebTransportSession(42, reason);
  CapsuleEncoder encoder;
  encoder.EncodeCapsule(capsule);
  auto buffer = encoder.GetBuffer();

  RefPtr<CapsuleParserListener> listener = new CapsuleParserListener();
  UniquePtr<CapsuleParser> parser = MakeUnique<CapsuleParser>(listener);
  bool res = parser->ProcessCapsuleData(buffer.Elements(), buffer.Length());
  ASSERT_FALSE(res);

  Maybe<nsresult> error = listener->GetErrorResult();
  ASSERT_TRUE(error.isSome());
  ASSERT_EQ(*error, NS_ERROR_UNEXPECTED);

  nsTArray<Capsule> parsed = listener->GetParsedCapsules();
  ASSERT_EQ(parsed.Length(), 0u);

  ASSERT_TRUE(parser->IsBufferEmpty());
}

TEST(TestCapsule, MultipleCapsules)
{
  nsCString reason("test");
  Capsule capsule1 = Capsule::CloseWebTransportSession(42, reason);

  nsTArray<uint8_t> data({0x1, 0x2, 0x3, 0x4});
  Capsule capsule2 = Capsule::WebTransportStreamData(0, true, std::move(data));

  CapsuleEncoder encoder;
  encoder.EncodeCapsule(capsule1);
  encoder.EncodeCapsule(capsule2);

  auto buffer = encoder.GetBuffer();

  RefPtr<CapsuleParserListener> listener = new CapsuleParserListener();
  UniquePtr<CapsuleParser> parser = MakeUnique<CapsuleParser>(listener);
  bool res = parser->ProcessCapsuleData(buffer.Elements(), buffer.Length());
  ASSERT_TRUE(res);

  Maybe<nsresult> error = listener->GetErrorResult();
  ASSERT_TRUE(error.isNothing());

  nsTArray<Capsule> parsed = listener->GetParsedCapsules();
  ASSERT_EQ(parsed.Length(), 2u);

  CloseWebTransportSessionCapsule& parsedCapsule =
      parsed[0].GetCloseWebTransportSessionCapsule();
  ASSERT_EQ(parsedCapsule.mStatus, 42u);
  ASSERT_EQ(parsedCapsule.mReason, reason);

  WebTransportStreamDataCapsule& streamData =
      parsed[1].GetWebTransportStreamDataCapsule();
  ASSERT_EQ(streamData.mID, 0u);
  ASSERT_EQ(streamData.mData.Length(), 4u);

  ASSERT_TRUE(parser->IsBufferEmpty());
}

TEST(TestCapsule, WouldBlock)
{
  nsCString reason("test");
  Capsule capsule1 = Capsule::CloseWebTransportSession(42, reason);

  nsTArray<uint8_t> data;
  for (uint32_t i = 0; i < 4096; i++) {
    data.AppendElement(0x2);
  }
  Capsule capsule2 = Capsule::WebTransportStreamData(0, true, std::move(data));

  CapsuleEncoder encoder;
  encoder.EncodeCapsule(capsule1);
  encoder.EncodeCapsule(capsule2);

  auto buffer = encoder.GetBuffer();

  const uint8_t* buf1 = buffer.Elements();
  uint32_t firstHalf = buffer.Length() / 2;

  const uint8_t* buf2 = buffer.Elements() + firstHalf;
  uint32_t secondHalf = buffer.Length() - firstHalf;

  RefPtr<CapsuleParserListener> listener = new CapsuleParserListener();
  UniquePtr<CapsuleParser> parser = MakeUnique<CapsuleParser>(listener);
  bool res = parser->ProcessCapsuleData(buf1, firstHalf);
  ASSERT_TRUE(res);

  Maybe<nsresult> error = listener->GetErrorResult();
  ASSERT_TRUE(error.isNothing());

  nsTArray<Capsule> parsed = listener->GetParsedCapsules();
  ASSERT_EQ(parsed.Length(), 1u);

  CloseWebTransportSessionCapsule& parsedCapsule =
      parsed[0].GetCloseWebTransportSessionCapsule();
  ASSERT_EQ(parsedCapsule.mStatus, 42u);
  ASSERT_EQ(parsedCapsule.mReason, reason);

  ASSERT_FALSE(parser->IsBufferEmpty());

  res = parser->ProcessCapsuleData(buf2, secondHalf);
  parsed = listener->GetParsedCapsules();
  ASSERT_EQ(parsed.Length(), 1u);

  WebTransportStreamDataCapsule& streamData =
      parsed[0].GetWebTransportStreamDataCapsule();
  ASSERT_EQ(streamData.mID, 0u);
  ASSERT_EQ(streamData.mData.Length(), 4096u);

  ASSERT_TRUE(parser->IsBufferEmpty());
}

TEST(TestCapsule, WouldBlock1)
{
  nsTArray<uint8_t> data;
  for (uint32_t i = 0; i < 4096; i++) {
    data.AppendElement(0x2);
  }
  Capsule capsule1 = Capsule::WebTransportStreamData(0, true, std::move(data));

  nsCString reason("test");
  Capsule capsule2 = Capsule::CloseWebTransportSession(42, reason);

  CapsuleEncoder encoder;
  encoder.EncodeCapsule(capsule1);
  encoder.EncodeCapsule(capsule2);

  auto buffer = encoder.GetBuffer();

  const uint8_t* buf1 = buffer.Elements();
  uint32_t firstHalf = buffer.Length() / 2;

  const uint8_t* buf2 = buffer.Elements() + firstHalf;
  uint32_t secondHalf = buffer.Length() - firstHalf;

  RefPtr<CapsuleParserListener> listener = new CapsuleParserListener();
  UniquePtr<CapsuleParser> parser = MakeUnique<CapsuleParser>(listener);
  bool res = parser->ProcessCapsuleData(buf1, firstHalf);
  ASSERT_TRUE(res);

  Maybe<nsresult> error = listener->GetErrorResult();
  ASSERT_TRUE(error.isNothing());

  nsTArray<Capsule> parsed = listener->GetParsedCapsules();
  ASSERT_EQ(parsed.Length(), 0u);

  ASSERT_FALSE(parser->IsBufferEmpty());

  res = parser->ProcessCapsuleData(buf2, secondHalf);
  parsed = listener->GetParsedCapsules();
  ASSERT_EQ(parsed.Length(), 2u);
  error = listener->GetErrorResult();
  ASSERT_TRUE(error.isNothing());

  WebTransportStreamDataCapsule& streamData =
      parsed[0].GetWebTransportStreamDataCapsule();
  ASSERT_EQ(streamData.mID, 0u);
  ASSERT_EQ(streamData.mData.Length(), 4096u);

  CloseWebTransportSessionCapsule& parsedCapsule =
      parsed[1].GetCloseWebTransportSessionCapsule();
  ASSERT_EQ(parsedCapsule.mStatus, 42u);
  ASSERT_EQ(parsedCapsule.mReason, reason);

  ASSERT_TRUE(parser->IsBufferEmpty());
}

TEST(TestCapsule, WouldBlock2)
{
  nsTArray<uint8_t> data;
  for (uint32_t i = 0; i < 4096; i++) {
    data.AppendElement(0x2);
  }
  Capsule capsule1 = Capsule::WebTransportStreamData(0, true, std::move(data));

  nsCString reason("test");
  Capsule capsule2 = Capsule::CloseWebTransportSession(42, reason);

  CapsuleEncoder encoder;
  encoder.EncodeCapsule(capsule1);
  encoder.EncodeCapsule(capsule2);

  auto buffer = encoder.GetBuffer();
  uint32_t totalLength = buffer.Length();

  // Split the buffer into three parts.
  uint32_t part1Length = totalLength / 3;
  uint32_t part2Length = totalLength / 3;
  uint32_t part3Length = totalLength - part1Length - part2Length;

  const uint8_t* buf1 = buffer.Elements();
  const uint8_t* buf2 = buffer.Elements() + part1Length;
  const uint8_t* buf3 = buffer.Elements() + part1Length + part2Length;

  RefPtr<CapsuleParserListener> listener = new CapsuleParserListener();
  UniquePtr<CapsuleParser> parser = MakeUnique<CapsuleParser>(listener);

  // Process first part.
  bool res = parser->ProcessCapsuleData(buf1, part1Length);
  ASSERT_TRUE(res);
  Maybe<nsresult> error = listener->GetErrorResult();
  ASSERT_TRUE(error.isNothing());
  nsTArray<Capsule> parsed = listener->GetParsedCapsules();
  // At this stage, we might not have a complete capsule yet.
  ASSERT_EQ(parsed.Length(), 0u);
  // The parser's internal buffer should not be empty.
  ASSERT_FALSE(parser->IsBufferEmpty());

  // Process second part.
  res = parser->ProcessCapsuleData(buf2, part2Length);
  ASSERT_TRUE(res);
  error = listener->GetErrorResult();
  ASSERT_TRUE(error.isNothing());
  // Still, we might be waiting for more data to form a complete capsule.
  parsed = listener->GetParsedCapsules();
  // It's possible that no complete capsule is parsed yet, depending on how the
  // data splits. We won't assert parsed.Length() here since it may vary.

  // Process third part.
  res = parser->ProcessCapsuleData(buf3, part3Length);
  ASSERT_TRUE(res);
  parsed = listener->GetParsedCapsules();
  // At the end, we should have parsed both capsules.
  ASSERT_EQ(parsed.Length(), 2u);
  error = listener->GetErrorResult();
  ASSERT_TRUE(error.isNothing());

  WebTransportStreamDataCapsule& streamData =
      parsed[0].GetWebTransportStreamDataCapsule();
  ASSERT_EQ(streamData.mID, 0u);
  ASSERT_EQ(streamData.mData.Length(), 4096u);

  CloseWebTransportSessionCapsule& parsedCapsule =
      parsed[1].GetCloseWebTransportSessionCapsule();
  ASSERT_EQ(parsedCapsule.mStatus, 42u);
  ASSERT_EQ(parsedCapsule.mReason, reason);

  ASSERT_TRUE(parser->IsBufferEmpty());
}

TEST(TestCapsule, WebTransportMaxDataCapsule)
{
  Capsule capsule = Capsule::WebTransportMaxData(16384);
  CapsuleEncoder encoder;
  encoder.EncodeCapsule(capsule);

  auto buffer = encoder.GetBuffer();

  Capsule::LogBuffer(buffer.Elements(), buffer.Length());

  RefPtr<CapsuleParserListener> listener = new CapsuleParserListener();
  UniquePtr<CapsuleParser> parser = MakeUnique<CapsuleParser>(listener);
  bool res = parser->ProcessCapsuleData(buffer.Elements(), buffer.Length());
  ASSERT_TRUE(res);

  Maybe<nsresult> error = listener->GetErrorResult();
  ASSERT_TRUE(error.isNothing());

  nsTArray<Capsule> parsed = listener->GetParsedCapsules();
  ASSERT_EQ(parsed.Length(), 1u);

  WebTransportMaxDataCapsule& parsedCapsule =
      parsed[0].GetWebTransportMaxDataCapsule();
  ASSERT_EQ(parsedCapsule.mMaxDataSize, 16384u);

  ASSERT_TRUE(parser->IsBufferEmpty());
}
