// Copyright (c) 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 "net/quic/quartc/quartc_stream.h"

#include "base/threading/thread_task_runner_handle.h"
#include "net/quic/core/crypto/quic_random.h"
#include "net/quic/core/quic_session.h"
#include "net/quic/core/quic_simple_buffer_allocator.h"
#include "net/quic/quartc/quartc_alarm_factory.h"
#include "net/quic/quartc/quartc_stream_interface.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace net {
namespace test {
namespace {

static const SpdyPriority kDefaultPriority = 3;
static const QuicStreamId kStreamId = 5;
static const QuartcStreamInterface::WriteParameters kDefaultParam;

// MockQuicSession that does not create streams and writes data from
// QuicStream to a string.
class MockQuicSession : public QuicSession {
 public:
  MockQuicSession(QuicConnection* connection,
                  const QuicConfig& config,
                  std::string* write_buffer)
      : QuicSession(connection, nullptr /*visitor*/, config),
        write_buffer_(write_buffer) {}

  // Writes outgoing data from QuicStream to a string.
  QuicConsumedData WritevData(
      QuicStream* stream,
      QuicStreamId id,
      QuicIOVector iovector,
      QuicStreamOffset offset,
      bool fin,
      QuicAckListenerInterface* ack_notifier_delegate) override {
    if (!writable_) {
      return QuicConsumedData(0, false);
    }

    const char* data = reinterpret_cast<const char*>(iovector.iov->iov_base);
    size_t len = iovector.total_length;
    write_buffer_->append(data, len);
    return QuicConsumedData(len, fin);
  }

  QuartcStream* CreateIncomingDynamicStream(QuicStreamId id) override {
    return nullptr;
  }

  QuartcStream* CreateOutgoingDynamicStream(SpdyPriority priority) override {
    return nullptr;
  }

  QuicCryptoStream* GetCryptoStream() override { return nullptr; }

  // Called by QuicStream when they want to close stream.
  void SendRstStream(QuicStreamId id,
                     QuicRstStreamErrorCode error,
                     QuicStreamOffset bytes_written) override {}

  // Sets whether data is written to buffer, or else if this is write blocked.
  void set_writable(bool writable) { writable_ = writable; }

  // Tracks whether the stream is write blocked and its priority.
  void RegisterReliableStream(QuicStreamId stream_id, SpdyPriority priority) {
    write_blocked_streams()->RegisterStream(stream_id, priority);
  }

  // The session take ownership of the stream.
  void ActivateReliableStream(std::unique_ptr<QuicStream> stream) {
    ActivateStream(std::move(stream));
  }

 private:
  // Stores written data from ReliableQuicStreamAdapter.
  std::string* write_buffer_;
  // Whether data is written to write_buffer_.
  bool writable_ = true;
};

// Packet writer that does nothing. This is required for QuicConnection but
// isn't used for writing data.
class DummyPacketWriter : public QuicPacketWriter {
 public:
  DummyPacketWriter() {}

  // QuicPacketWriter overrides.
  WriteResult WritePacket(const char* buffer,
                          size_t buf_len,
                          const IPAddress& self_address,
                          const IPEndPoint& peer_address,
                          PerPacketOptions* options) override {
    return WriteResult(WRITE_STATUS_ERROR, 0);
  }

  bool IsWriteBlockedDataBuffered() const override { return false; }

  bool IsWriteBlocked() const override { return false; };

  void SetWritable() override {}

  QuicByteCount GetMaxPacketSize(
      const IPEndPoint& peer_address) const override {
    return 0;
  }
};

class MockQuartcStreamDelegate : public QuartcStreamInterface::Delegate {
 public:
  MockQuartcStreamDelegate(int id, std::string& read_buffer)
      : id_(id), read_buffer_(read_buffer) {}

  void OnBufferedAmountDecrease(QuartcStreamInterface* stream) override {
    queued_bytes_amount_ = stream->buffered_amount();
  }

  void OnReceived(QuartcStreamInterface* stream,
                  const char* data,
                  size_t size) override {
    EXPECT_EQ(id_, stream->stream_id());
    read_buffer_.append(data, size);
  }

  void OnClose(QuartcStreamInterface* stream, int error_code) override {
    closed_ = true;
  }

  bool closed() { return closed_; }

  int queued_bytes_amount() { return queued_bytes_amount_; }

 protected:
  uint32_t id_;
  // Data read by the QuicStream.
  std::string& read_buffer_;
  // Whether the QuicStream is closed.
  bool closed_ = false;
  int queued_bytes_amount_ = -1;
};

class QuartcStreamTest : public ::testing::Test,
                         public QuicConnectionHelperInterface {
 public:
  void CreateReliableQuicStream() {
    // Arbitrary values for QuicConnection.
    Perspective perspective = Perspective::IS_SERVER;
    IPAddress ip(0, 0, 0, 0);
    bool owns_writer = true;
    alarm_factory_.reset(new QuartcAlarmFactory(
        base::ThreadTaskRunnerHandle::Get().get(), GetClock()));

    connection_.reset(new QuicConnection(
        0, IPEndPoint(ip, 0), this /*QuicConnectionHelperInterface*/,
        alarm_factory_.get(), new DummyPacketWriter(), owns_writer, perspective,
        AllSupportedVersions()));

    session_.reset(
        new MockQuicSession(connection_.get(), QuicConfig(), &write_buffer_));
    mock_stream_delegate_.reset(
        new MockQuartcStreamDelegate(kStreamId, read_buffer_));
    stream_ = new QuartcStream(kStreamId, session_.get());
    stream_->SetDelegate(mock_stream_delegate_.get());
    session_->RegisterReliableStream(stream_->stream_id(), kDefaultPriority);
    session_->ActivateReliableStream(std::unique_ptr<QuartcStream>(stream_));
  }

  const QuicClock* GetClock() const override { return &clock_; }

  QuicRandom* GetRandomGenerator() override {
    return QuicRandom::GetInstance();
  }

  QuicBufferAllocator* GetBufferAllocator() override {
    return &buffer_allocator_;
  }

 protected:
  // The QuicSession will take the ownership.
  QuartcStream* stream_;
  std::unique_ptr<MockQuartcStreamDelegate> mock_stream_delegate_;
  std::unique_ptr<MockQuicSession> session_;
  // Data written by the ReliableQuicStreamAdapterTest.
  std::string write_buffer_;
  // Data read by the ReliableQuicStreamAdapterTest.
  std::string read_buffer_;
  std::unique_ptr<QuartcAlarmFactory> alarm_factory_;
  std::unique_ptr<QuicConnection> connection_;
  // Used to implement the QuicConnectionHelperInterface.
  SimpleBufferAllocator buffer_allocator_;
  QuicClock clock_;
};

// Write an entire string.
TEST_F(QuartcStreamTest, WriteDataWhole) {
  CreateReliableQuicStream();
  stream_->Write("Foo bar", 7, kDefaultParam);
  EXPECT_EQ("Foo bar", write_buffer_);
}

// Write part of a string.
TEST_F(QuartcStreamTest, WriteDataPartial) {
  CreateReliableQuicStream();
  stream_->Write("Foo bar", 5, kDefaultParam);
  EXPECT_EQ("Foo b", write_buffer_);
}

// Test that strings are buffered correctly.
TEST_F(QuartcStreamTest, BufferData) {
  CreateReliableQuicStream();

  session_->set_writable(false);
  stream_->Write("Foo bar", 7, kDefaultParam);
  // The data will be buffered.
  EXPECT_EQ(0ul, write_buffer_.size());
  EXPECT_TRUE(stream_->HasBufferedData());
  EXPECT_EQ(-1, mock_stream_delegate_->queued_bytes_amount());
  // The session is writable and the buffered data amount will change.
  session_->set_writable(true);
  stream_->OnCanWrite();
  EXPECT_EQ(0, mock_stream_delegate_->queued_bytes_amount());
  EXPECT_FALSE(stream_->HasBufferedData());
  EXPECT_EQ("Foo bar", write_buffer_);

  stream_->Write("xyzzy", 5, kDefaultParam);
  EXPECT_EQ("Foo barxyzzy", write_buffer_);
}

// Read an entire string.
TEST_F(QuartcStreamTest, ReadDataWhole) {
  CreateReliableQuicStream();
  QuicStreamFrame frame(kStreamId, false, 0, "Hello, World!");
  stream_->OnStreamFrame(frame);

  EXPECT_EQ("Hello, World!", read_buffer_);
}

// Read part of a string.
TEST_F(QuartcStreamTest, ReadDataPartial) {
  CreateReliableQuicStream();
  QuicStreamFrame frame(kStreamId, false, 0, "Hello, World!");
  frame.data_length = 5;
  stream_->OnStreamFrame(frame);

  EXPECT_EQ("Hello", read_buffer_);
}

// Test that closing the stream results in a callback.
TEST_F(QuartcStreamTest, CloseStream) {
  CreateReliableQuicStream();
  EXPECT_FALSE(mock_stream_delegate_->closed());
  stream_->OnClose();
  EXPECT_TRUE(mock_stream_delegate_->closed());
}

}  // namespace
}  // namespace test
}  // namespace net
