/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "perfetto/ext/tracing/core/trace_packet.h"

#include <string>

#include "protos/perfetto/trace/test_event.gen.h"
#include "protos/perfetto/trace/trace.gen.h"
#include "protos/perfetto/trace/trace_packet.gen.h"
#include "test/gtest_and_gmock.h"

namespace perfetto {
namespace {

TEST(TracePacketTest, Simple) {
  protos::gen::TracePacket proto;
  proto.mutable_for_testing()->set_str("string field");
  std::string ser_buf = proto.SerializeAsString();
  TracePacket tp;
  tp.AddSlice(ser_buf.data(), ser_buf.size());
  auto slice = tp.slices().begin();
  ASSERT_NE(tp.slices().end(), slice);
  ASSERT_EQ(ser_buf.data(), slice->start);
  ASSERT_EQ(ser_buf.size(), slice->size);
  ASSERT_EQ(tp.slices().end(), ++slice);

  protos::gen::TracePacket decoded_packet;
  ASSERT_TRUE(decoded_packet.ParseFromString(tp.GetRawBytesForTesting()));
  ASSERT_EQ(proto.for_testing().str(), decoded_packet.for_testing().str());
}

TEST(TracePacketTest, Sliced) {
  protos::gen::TracePacket proto;
  proto.mutable_for_testing()->set_str(
      "this is an arbitrarily long string ........................");
  std::string ser_buf = proto.SerializeAsString();
  TracePacket tp;
  tp.AddSlice({ser_buf.data(), 3});
  tp.AddSlice({ser_buf.data() + 3, 5});
  tp.AddSlice({ser_buf.data() + 3 + 5, ser_buf.size() - 3 - 5});
  ASSERT_EQ(ser_buf.size(), tp.size());

  auto slice = tp.slices().begin();
  ASSERT_NE(tp.slices().end(), slice);
  ASSERT_EQ(ser_buf.data(), slice->start);
  ASSERT_EQ(3u, slice->size);

  ASSERT_NE(tp.slices().end(), ++slice);
  ASSERT_EQ(ser_buf.data() + 3, slice->start);
  ASSERT_EQ(5u, slice->size);

  ASSERT_NE(tp.slices().end(), ++slice);
  ASSERT_EQ(ser_buf.data() + 3 + 5, slice->start);
  ASSERT_EQ(ser_buf.size() - 3 - 5, slice->size);

  ASSERT_EQ(tp.slices().end(), ++slice);

  protos::gen::TracePacket decoded_packet;
  ASSERT_TRUE(decoded_packet.ParseFromString(tp.GetRawBytesForTesting()));
  ASSERT_EQ(proto.for_testing().str(), decoded_packet.for_testing().str());
}

TEST(TracePacketTest, Corrupted) {
  protos::gen::TracePacket proto;
  proto.mutable_for_testing()->set_str("string field");
  std::string ser_buf = proto.SerializeAsString();
  TracePacket tp;
  tp.AddSlice({ser_buf.data(), ser_buf.size() - 2});  // corrupted.
  protos::gen::TracePacket decoded_packet;
  ASSERT_FALSE(decoded_packet.ParseFromString(tp.GetRawBytesForTesting()));
}

// Tests that the GetProtoPreamble() logic returns a valid preamble that allows
// to encode a TracePacket as a field of the root trace.proto message.
TEST(TracePacketTest, GetProtoPreamble) {
  char* preamble;
  size_t preamble_size;

  // Test empty packet.
  TracePacket tp;
  std::tie(preamble, preamble_size) = tp.GetProtoPreamble();
  ASSERT_EQ(2u, preamble_size);
  ASSERT_EQ(0, preamble[1]);

  // Test packet with one slice.
  protos::gen::TracePacket tp_proto;
  char payload[257];
  for (size_t i = 0; i < sizeof(payload) - 1; i++)
    payload[i] = 'a' + (i % 16);
  payload[sizeof(payload) - 1] = '\0';
  tp_proto.mutable_for_testing()->set_str(payload);
  std::string ser_buf = tp_proto.SerializeAsString();
  tp.AddSlice({ser_buf.data(), ser_buf.size()});

  std::tie(preamble, preamble_size) = tp.GetProtoPreamble();
  ASSERT_EQ(3u, preamble_size);

  // Verify that the content is actually parsable using libprotobuf.
  char buf[512];
  memcpy(buf, preamble, preamble_size);
  ASSERT_EQ(1u, tp.slices().size());
  memcpy(&buf[preamble_size], tp.slices()[0].start, tp.slices()[0].size);
  protos::gen::Trace trace;
  ASSERT_TRUE(trace.ParseFromArray(buf, preamble_size + tp.size()));
  ASSERT_EQ(1, trace.packet_size());
  ASSERT_EQ(payload, trace.packet()[0].for_testing().str());
}

TEST(TracePacketTest, MoveOperators) {
  char buf1[5]{};
  char buf2[7]{};

  TracePacket tp;
  tp.AddSlice(buf1, sizeof(buf1));
  tp.AddSlice(buf2, sizeof(buf2));
  tp.AddSlice(Slice::Allocate(11));

  TracePacket moved_tp(std::move(tp));
  ASSERT_EQ(0u, tp.size());
  ASSERT_TRUE(tp.slices().empty());
  ASSERT_EQ(3u, moved_tp.slices().size());
  ASSERT_EQ(5u + 7u + 11u, moved_tp.size());

  TracePacket moved_tp_2;
  moved_tp_2 = std::move(moved_tp);
  ASSERT_EQ(0u, moved_tp.size());
  ASSERT_TRUE(moved_tp.slices().empty());
  ASSERT_EQ(3u, moved_tp_2.slices().size());
  ASSERT_EQ(5u + 7u + 11u, moved_tp_2.size());
}

TEST(TracePacketTest, Clear) {
  std::string_view data = "slice_data";

  TracePacket tp;
  tp.AddSlice(data.data(), data.size());
  tp.set_buffer_index_for_stats(10);
  ASSERT_EQ(tp.size(), data.size());
  ASSERT_EQ(tp.slices().size(), 1u);
  ASSERT_EQ(tp.buffer_index_for_stats(), 10);
  ASSERT_EQ(tp.GetRawBytesForTesting(), data);

  tp.Clear();
  ASSERT_EQ(tp.size(), 0u);
  ASSERT_EQ(tp.slices().size(), 0u);
  ASSERT_EQ(tp.buffer_index_for_stats(), std::nullopt);
  ASSERT_EQ(tp.GetRawBytesForTesting(), "");
}

}  // namespace
}  // namespace perfetto
