/*
 *  Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */
#include "net/dcsctp/rx/interleaved_reassembly_streams.h"

#include <cstdint>
#include <utility>

#include "net/dcsctp/common/handover_testing.h"
#include "net/dcsctp/common/internal_types.h"
#include "net/dcsctp/common/sequence_numbers.h"
#include "net/dcsctp/packet/chunk/forward_tsn_common.h"
#include "net/dcsctp/packet/chunk/iforward_tsn_chunk.h"
#include "net/dcsctp/packet/data.h"
#include "net/dcsctp/public/dcsctp_handover_state.h"
#include "net/dcsctp/public/dcsctp_message.h"
#include "net/dcsctp/public/types.h"
#include "net/dcsctp/rx/reassembly_streams.h"
#include "net/dcsctp/testing/data_generator.h"
#include "test/gmock.h"
#include "test/gtest.h"

namespace dcsctp {
namespace {
using ::testing::ElementsAre;
using ::testing::Field;
using ::testing::MockFunction;
using ::testing::NiceMock;
using ::testing::Property;

class InterleavedReassemblyStreamsTest : public testing::Test {
 protected:
  UnwrappedTSN tsn(uint32_t value) { return tsn_.Unwrap(TSN(value)); }

  InterleavedReassemblyStreamsTest() {}
  DataGenerator gen_;
  UnwrappedTSN::Unwrapper tsn_;
};

TEST_F(InterleavedReassemblyStreamsTest,
       AddUnorderedMessageReturnsCorrectSize) {
  NiceMock<MockFunction<ReassemblyStreams::OnAssembledMessage>> on_assembled;

  InterleavedReassemblyStreams streams("", on_assembled.AsStdFunction());

  EXPECT_EQ(streams.Add(tsn(1), gen_.Unordered({1}, "B")), 1);
  EXPECT_EQ(streams.Add(tsn(2), gen_.Unordered({2, 3, 4})), 3);
  EXPECT_EQ(streams.Add(tsn(3), gen_.Unordered({5, 6})), 2);
  // Adding the end fragment should make it empty again.
  EXPECT_EQ(streams.Add(tsn(4), gen_.Unordered({7}, "E")), -6);
}

TEST_F(InterleavedReassemblyStreamsTest,
       AddSimpleOrderedMessageReturnsCorrectSize) {
  NiceMock<MockFunction<ReassemblyStreams::OnAssembledMessage>> on_assembled;

  InterleavedReassemblyStreams streams("", on_assembled.AsStdFunction());

  EXPECT_EQ(streams.Add(tsn(1), gen_.Ordered({1}, "B")), 1);
  EXPECT_EQ(streams.Add(tsn(2), gen_.Ordered({2, 3, 4})), 3);
  EXPECT_EQ(streams.Add(tsn(3), gen_.Ordered({5, 6})), 2);
  EXPECT_EQ(streams.Add(tsn(4), gen_.Ordered({7}, "E")), -6);
}

TEST_F(InterleavedReassemblyStreamsTest,
       AddMoreComplexOrderedMessageReturnsCorrectSize) {
  NiceMock<MockFunction<ReassemblyStreams::OnAssembledMessage>> on_assembled;

  InterleavedReassemblyStreams streams("", on_assembled.AsStdFunction());

  EXPECT_EQ(streams.Add(tsn(1), gen_.Ordered({1}, "B")), 1);
  Data late = gen_.Ordered({2, 3, 4});
  EXPECT_EQ(streams.Add(tsn(3), gen_.Ordered({5, 6})), 2);
  EXPECT_EQ(streams.Add(tsn(4), gen_.Ordered({7}, "E")), 1);

  EXPECT_EQ(streams.Add(tsn(5), gen_.Ordered({1}, "BE")), 1);
  EXPECT_EQ(streams.Add(tsn(6), gen_.Ordered({5, 6}, "B")), 2);
  EXPECT_EQ(streams.Add(tsn(7), gen_.Ordered({7}, "E")), 1);
  EXPECT_EQ(streams.Add(tsn(2), std::move(late)), -8);
}

TEST_F(InterleavedReassemblyStreamsTest,
       DeleteUnorderedMessageReturnsCorrectSize) {
  NiceMock<MockFunction<ReassemblyStreams::OnAssembledMessage>> on_assembled;

  InterleavedReassemblyStreams streams("", on_assembled.AsStdFunction());

  EXPECT_EQ(streams.Add(tsn(1), gen_.Unordered({1}, "B")), 1);
  EXPECT_EQ(streams.Add(tsn(2), gen_.Unordered({2, 3, 4})), 3);
  EXPECT_EQ(streams.Add(tsn(3), gen_.Unordered({5, 6})), 2);

  IForwardTsnChunk::SkippedStream skipped[] = {
      IForwardTsnChunk::SkippedStream(IsUnordered(true), StreamID(1), MID(0))};
  EXPECT_EQ(streams.HandleForwardTsn(tsn(3), skipped), 6u);
}

TEST_F(InterleavedReassemblyStreamsTest,
       DeleteSimpleOrderedMessageReturnsCorrectSize) {
  NiceMock<MockFunction<ReassemblyStreams::OnAssembledMessage>> on_assembled;

  InterleavedReassemblyStreams streams("", on_assembled.AsStdFunction());

  EXPECT_EQ(streams.Add(tsn(1), gen_.Ordered({1}, "B")), 1);
  EXPECT_EQ(streams.Add(tsn(2), gen_.Ordered({2, 3, 4})), 3);
  EXPECT_EQ(streams.Add(tsn(3), gen_.Ordered({5, 6})), 2);

  IForwardTsnChunk::SkippedStream skipped[] = {
      IForwardTsnChunk::SkippedStream(IsUnordered(false), StreamID(1), MID(0))};
  EXPECT_EQ(streams.HandleForwardTsn(tsn(3), skipped), 6u);
}

TEST_F(InterleavedReassemblyStreamsTest,
       DeleteManyOrderedMessagesReturnsCorrectSize) {
  NiceMock<MockFunction<ReassemblyStreams::OnAssembledMessage>> on_assembled;

  InterleavedReassemblyStreams streams("", on_assembled.AsStdFunction());

  EXPECT_EQ(streams.Add(tsn(1), gen_.Ordered({1}, "B")), 1);
  gen_.Ordered({2, 3, 4});
  EXPECT_EQ(streams.Add(tsn(3), gen_.Ordered({5, 6})), 2);
  EXPECT_EQ(streams.Add(tsn(4), gen_.Ordered({7}, "E")), 1);

  EXPECT_EQ(streams.Add(tsn(5), gen_.Ordered({1}, "BE")), 1);
  EXPECT_EQ(streams.Add(tsn(6), gen_.Ordered({5, 6}, "B")), 2);
  EXPECT_EQ(streams.Add(tsn(7), gen_.Ordered({7}, "E")), 1);

  // Expire all three messages
  IForwardTsnChunk::SkippedStream skipped[] = {
      IForwardTsnChunk::SkippedStream(IsUnordered(false), StreamID(1), MID(2))};
  EXPECT_EQ(streams.HandleForwardTsn(tsn(8), skipped), 8u);
}

TEST_F(InterleavedReassemblyStreamsTest,
       DeleteOrderedMessageDelivesTwoReturnsCorrectSize) {
  NiceMock<MockFunction<ReassemblyStreams::OnAssembledMessage>> on_assembled;

  InterleavedReassemblyStreams streams("", on_assembled.AsStdFunction());

  EXPECT_EQ(streams.Add(tsn(1), gen_.Ordered({1}, "B")), 1);
  gen_.Ordered({2, 3, 4});
  EXPECT_EQ(streams.Add(tsn(3), gen_.Ordered({5, 6})), 2);
  EXPECT_EQ(streams.Add(tsn(4), gen_.Ordered({7}, "E")), 1);

  EXPECT_EQ(streams.Add(tsn(5), gen_.Ordered({1}, "BE")), 1);
  EXPECT_EQ(streams.Add(tsn(6), gen_.Ordered({5, 6}, "B")), 2);
  EXPECT_EQ(streams.Add(tsn(7), gen_.Ordered({7}, "E")), 1);

  // The first ordered message expire, and the following two are delivered.
  IForwardTsnChunk::SkippedStream skipped[] = {
      IForwardTsnChunk::SkippedStream(IsUnordered(false), StreamID(1), MID(0))};
  EXPECT_EQ(streams.HandleForwardTsn(tsn(4), skipped), 8u);
}

TEST_F(InterleavedReassemblyStreamsTest, CanReassembleFastPathUnordered) {
  NiceMock<MockFunction<ReassemblyStreams::OnAssembledMessage>> on_assembled;

  {
    testing::InSequence s;
    EXPECT_CALL(on_assembled,
                Call(ElementsAre(tsn(1)),
                     Property(&DcSctpMessage::payload, ElementsAre(1))));
    EXPECT_CALL(on_assembled,
                Call(ElementsAre(tsn(3)),
                     Property(&DcSctpMessage::payload, ElementsAre(3))));
    EXPECT_CALL(on_assembled,
                Call(ElementsAre(tsn(2)),
                     Property(&DcSctpMessage::payload, ElementsAre(2))));
    EXPECT_CALL(on_assembled,
                Call(ElementsAre(tsn(4)),
                     Property(&DcSctpMessage::payload, ElementsAre(4))));
  }

  InterleavedReassemblyStreams streams("", on_assembled.AsStdFunction());

  EXPECT_EQ(streams.Add(tsn(1), gen_.Unordered({1}, "BE")), 0);
  EXPECT_EQ(streams.Add(tsn(3), gen_.Unordered({3}, "BE")), 0);
  EXPECT_EQ(streams.Add(tsn(2), gen_.Unordered({2}, "BE")), 0);
  EXPECT_EQ(streams.Add(tsn(4), gen_.Unordered({4}, "BE")), 0);
}

TEST_F(InterleavedReassemblyStreamsTest, CanReassembleFastPathOrdered) {
  NiceMock<MockFunction<ReassemblyStreams::OnAssembledMessage>> on_assembled;

  {
    testing::InSequence s;
    EXPECT_CALL(on_assembled,
                Call(ElementsAre(tsn(1)),
                     Property(&DcSctpMessage::payload, ElementsAre(1))));
    EXPECT_CALL(on_assembled,
                Call(ElementsAre(tsn(2)),
                     Property(&DcSctpMessage::payload, ElementsAre(2))));
    EXPECT_CALL(on_assembled,
                Call(ElementsAre(tsn(3)),
                     Property(&DcSctpMessage::payload, ElementsAre(3))));
    EXPECT_CALL(on_assembled,
                Call(ElementsAre(tsn(4)),
                     Property(&DcSctpMessage::payload, ElementsAre(4))));
  }

  InterleavedReassemblyStreams streams("", on_assembled.AsStdFunction());

  Data data1 = gen_.Ordered({1}, "BE");
  Data data2 = gen_.Ordered({2}, "BE");
  Data data3 = gen_.Ordered({3}, "BE");
  Data data4 = gen_.Ordered({4}, "BE");
  EXPECT_EQ(streams.Add(tsn(1), std::move(data1)), 0);
  EXPECT_EQ(streams.Add(tsn(3), std::move(data3)), 1);
  EXPECT_EQ(streams.Add(tsn(2), std::move(data2)), -1);
  EXPECT_EQ(streams.Add(tsn(4), std::move(data4)), 0);
}

TEST_F(InterleavedReassemblyStreamsTest, CanHandoverOrderedStreams) {
  InterleavedReassemblyStreams streams1("", [](auto...) {});

  EXPECT_EQ(streams1.Add(tsn(1), gen_.Ordered({1}, "B")), 1);
  EXPECT_EQ(streams1.GetHandoverReadiness(),
            HandoverReadinessStatus(
                HandoverUnreadinessReason::kOrderedStreamHasUnassembledChunks));
  EXPECT_EQ(streams1.Add(tsn(2), gen_.Ordered({2, 3, 4}, "E")), -1);
  EXPECT_TRUE(streams1.GetHandoverReadiness().IsReady());

  DcSctpSocketHandoverState state;
  streams1.AddHandoverState(state);
  g_handover_state_transformer_for_test(&state);

  MockFunction<ReassemblyStreams::OnAssembledMessage> on_assembled;
  EXPECT_CALL(on_assembled,
              Call(ElementsAre(tsn(3)),
                   Property(&DcSctpMessage::payload, ElementsAre(5, 6, 7, 8))));

  InterleavedReassemblyStreams streams2("", on_assembled.AsStdFunction());
  streams2.RestoreFromState(state);
  Data data = gen_.Ordered({5, 6, 7, 8}, "BE");
  EXPECT_EQ(data.mid, MID(1));
  EXPECT_EQ(streams2.Add(tsn(3), std::move(data)), 0);
}

TEST_F(InterleavedReassemblyStreamsTest, CanHandoverUnorderedStreams) {
  InterleavedReassemblyStreams streams1("", [](auto...) {});

  EXPECT_EQ(streams1.Add(tsn(1), gen_.Unordered({1}, "B")), 1);
  EXPECT_EQ(
      streams1.GetHandoverReadiness(),
      HandoverReadinessStatus(
          HandoverUnreadinessReason::kUnorderedStreamHasUnassembledChunks));
  EXPECT_EQ(streams1.Add(tsn(2), gen_.Unordered({2, 3, 4}, "E")), -1);
  EXPECT_TRUE(streams1.GetHandoverReadiness().IsReady());

  DcSctpSocketHandoverState state;
  streams1.AddHandoverState(state);
  g_handover_state_transformer_for_test(&state);

  MockFunction<ReassemblyStreams::OnAssembledMessage> on_assembled;
  EXPECT_CALL(on_assembled,
              Call(ElementsAre(tsn(3)),
                   Property(&DcSctpMessage::payload, ElementsAre(5, 6, 7, 8))));

  InterleavedReassemblyStreams streams2("", on_assembled.AsStdFunction());
  streams2.RestoreFromState(state);
  Data data = gen_.Unordered({5, 6, 7, 8}, "BE");
  EXPECT_EQ(data.mid, MID(1));
  EXPECT_EQ(streams2.Add(tsn(3), std::move(data)), 0);
}
}  // namespace
}  // namespace dcsctp
