/*
 * Copyright (C) 2022 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 "src/trace_processor/util/zip_reader.h"

#include <cstdint>
#include <cstring>
#include <ctime>
#include <string>
#include <vector>

#include "perfetto/base/build_config.h"
#include "perfetto/base/status.h"
#include "perfetto/ext/base/string_view.h"
#include "perfetto/trace_processor/trace_blob.h"
#include "perfetto/trace_processor/trace_blob_view.h"
#include "src/base/test/status_matchers.h"
#include "test/gtest_and_gmock.h"

namespace perfetto::trace_processor::util {
namespace {
using base::gtest_matchers::IsError;

// This zip file contains the following:
// Zip file size: 386 bytes, number of entries: 2
// -rw-r--r--  3.0 unx        4 tx stor 22-Jul-25 16:43 stored_file
// -rw-r--r--  3.0 unx       89 tx defN 22-Jul-25 18:34 dir/deflated_file
// 2 files, 92 bytes uncompressed, 52 bytes compressed:  43.5%
//
// /stored_file      content: "foo"
// dir/deflated_file content: 2x "The quick brown fox jumps over the lazy dog\n"
const uint8_t kTestZip[] = {
    0x50, 0x4b, 0x03, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6a, 0x85,
    0xf9, 0x54, 0xa8, 0x65, 0x32, 0x7e, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00,
    0x00, 0x00, 0x0b, 0x00, 0x1c, 0x00, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64,
    0x5f, 0x66, 0x69, 0x6c, 0x65, 0x55, 0x54, 0x09, 0x00, 0x03, 0x17, 0xba,
    0xde, 0x62, 0x44, 0xba, 0xde, 0x62, 0x75, 0x78, 0x0b, 0x00, 0x01, 0x04,
    0xce, 0x69, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x66, 0x6f, 0x6f,
    0x0a, 0x50, 0x4b, 0x03, 0x04, 0x14, 0x00, 0x00, 0x00, 0x08, 0x00, 0x47,
    0x94, 0xf9, 0x54, 0xf2, 0x03, 0x92, 0x3c, 0x34, 0x00, 0x00, 0x00, 0x59,
    0x00, 0x00, 0x00, 0x11, 0x00, 0x1c, 0x00, 0x64, 0x69, 0x72, 0x2f, 0x64,
    0x65, 0x66, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x65,
    0x55, 0x54, 0x09, 0x00, 0x03, 0x15, 0xd4, 0xde, 0x62, 0xf4, 0xba, 0xde,
    0x62, 0x75, 0x78, 0x0b, 0x00, 0x01, 0x04, 0xce, 0x69, 0x02, 0x00, 0x04,
    0x00, 0x00, 0x00, 0x00, 0x0b, 0xc9, 0x48, 0x55, 0x28, 0x2c, 0xcd, 0x4c,
    0xce, 0x56, 0x48, 0x2a, 0xca, 0x2f, 0xcf, 0x53, 0x48, 0xcb, 0xaf, 0x50,
    0xc8, 0x2a, 0xcd, 0x2d, 0x28, 0x56, 0xc8, 0x2f, 0x4b, 0x2d, 0x52, 0x28,
    0x01, 0x4a, 0xe7, 0x24, 0x56, 0x55, 0x2a, 0xa4, 0xe4, 0xa7, 0x73, 0x85,
    0x10, 0xa9, 0x36, 0xad, 0x08, 0xa8, 0x18, 0x00, 0x50, 0x4b, 0x01, 0x02,
    0x1e, 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6a, 0x85, 0xf9, 0x54,
    0xa8, 0x65, 0x32, 0x7e, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
    0x0b, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
    0xa4, 0x81, 0x00, 0x00, 0x00, 0x00, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64,
    0x5f, 0x66, 0x69, 0x6c, 0x65, 0x55, 0x54, 0x05, 0x00, 0x03, 0x17, 0xba,
    0xde, 0x62, 0x75, 0x78, 0x0b, 0x00, 0x01, 0x04, 0xce, 0x69, 0x02, 0x00,
    0x04, 0x00, 0x00, 0x00, 0x00, 0x50, 0x4b, 0x01, 0x02, 0x1e, 0x03, 0x14,
    0x00, 0x00, 0x00, 0x08, 0x00, 0x47, 0x94, 0xf9, 0x54, 0xf2, 0x03, 0x92,
    0x3c, 0x34, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x11, 0x00, 0x18,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xa4, 0x81, 0x49,
    0x00, 0x00, 0x00, 0x64, 0x69, 0x72, 0x2f, 0x64, 0x65, 0x66, 0x6c, 0x61,
    0x74, 0x65, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x55, 0x54, 0x05, 0x00,
    0x03, 0x15, 0xd4, 0xde, 0x62, 0x75, 0x78, 0x0b, 0x00, 0x01, 0x04, 0xce,
    0x69, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x50, 0x4b, 0x05, 0x06,
    0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0xa8, 0x00, 0x00, 0x00,
    0xc8, 0x00, 0x00, 0x00, 0x00, 0x00};

std::string vec2str(const std::vector<uint8_t>& vec) {
  return {reinterpret_cast<const char*>(vec.data()), vec.size()};
}

void ValidateTestZip(ZipReader& zr) {
  ASSERT_EQ(zr.files().size(), 2u);

  std::vector<uint8_t> dec;
  ASSERT_EQ(zr.files()[0].name(), "stored_file");
  ASSERT_EQ(zr.files()[0].GetDatetimeStr(), "2022-07-25 16:43:20");

  ASSERT_EQ(zr.files()[1].name(), "dir/deflated_file");
  ASSERT_EQ(zr.files()[1].GetDatetimeStr(), "2022-07-25 18:34:14");

  // This file is STORE-d and doesn't require any decompression.
  auto res = zr.files()[0].Decompress(&dec);
  ASSERT_TRUE(res.ok()) << res.message();
  ASSERT_EQ(dec.size(), 4u);
  ASSERT_EQ(vec2str(dec), "foo\n");

  // This file is DEFLATE-d and requires zlib.
#if PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
  res = zr.files()[1].Decompress(&dec);
  ASSERT_TRUE(res.ok()) << res.message();
  ASSERT_EQ(dec.size(), 89u);
  ASSERT_EQ(vec2str(dec),
            "The quick brown fox jumps over the lazy dog\n"
            "The quick brown fox jumps over the lazy frog\n");
#endif
}

TEST(ZipReaderTest, ValidZip_OneShotParse) {
  ZipReader zr;
  ASSERT_OK(
      zr.Parse(TraceBlobView(TraceBlob::CopyFrom(kTestZip, sizeof(kTestZip)))));
  ValidateTestZip(zr);
}

TEST(ZipReaderTest, ValidZip_OneByteChunks) {
  ZipReader zr;
  for (auto i : kTestZip) {
    ASSERT_OK(zr.Parse(TraceBlobView(TraceBlob::CopyFrom(&i, 1))));
  }
  ValidateTestZip(zr);
}

TEST(ZipReaderTest, MalformedZip_InvalidSignature) {
  ZipReader zr;
  uint8_t content[sizeof(kTestZip)];
  memcpy(content, kTestZip, sizeof(kTestZip));
  content[0] = 0xff;  // Invalid signature
  ASSERT_THAT(
      zr.Parse(TraceBlobView(TraceBlob::CopyFrom(content, sizeof(kTestZip)))),
      IsError());
  ASSERT_EQ(zr.files().size(), 0u);
}

TEST(ZipReaderTest, MalformedZip_VersionTooHigh) {
  ZipReader zr;
  uint8_t content[sizeof(kTestZip)];
  memcpy(content, kTestZip, sizeof(kTestZip));
  content[5] = 9;  // Version: 9.0
  ASSERT_THAT(
      zr.Parse(TraceBlobView(TraceBlob::CopyFrom(content, sizeof(kTestZip)))),
      IsError());
  ASSERT_EQ(zr.files().size(), 0u);
}

TEST(ZipReaderTest, TruncatedZip) {
  ZipReader zr;
  ASSERT_OK(zr.Parse(TraceBlobView(TraceBlob::CopyFrom(kTestZip, 40))));
  ASSERT_EQ(zr.files().size(), 0u);
}

TEST(ZipReaderTest, Find) {
  ZipReader zr;
  ASSERT_OK(
      zr.Parse(TraceBlobView(TraceBlob::CopyFrom(kTestZip, sizeof(kTestZip)))));
  ASSERT_EQ(zr.Find("stored_file")->name(), "stored_file");
  ASSERT_EQ(zr.Find("dir/deflated_file")->name(), "dir/deflated_file");
  ASSERT_EQ(nullptr, zr.Find("stored_f"));
  ASSERT_EQ(nullptr, zr.Find("_file*"));
  ASSERT_EQ(nullptr, zr.Find("dirz/deflated_file"));
}

// All the tests below require zlib.
#if PERFETTO_BUILDFLAG(PERFETTO_ZLIB)

TEST(ZipReaderTest, ValidZip_DecompressLines) {
  ZipReader zr;
  ASSERT_OK(
      zr.Parse(TraceBlobView(TraceBlob::CopyFrom(kTestZip, sizeof(kTestZip)))));
  ValidateTestZip(zr);
  int num_callbacks = 0;
  zr.files()[1].DecompressLines(
      [&](const std::vector<base::StringView>& lines) {
        ASSERT_EQ(num_callbacks++, 0);
        ASSERT_TRUE(lines.size() == 2);
        ASSERT_EQ(lines[0].ToStdString(),
                  "The quick brown fox jumps over the lazy dog");
        ASSERT_EQ(lines[1].ToStdString(),
                  "The quick brown fox jumps over the lazy frog");
      });

  ASSERT_EQ(num_callbacks, 1);
}

TEST(ZipReaderTest, MalformedZip_DecomprError) {
  ZipReader zr;
  uint8_t content[sizeof(kTestZip)];
  memcpy(content, kTestZip, sizeof(kTestZip));

  // The 2nd file header starts at 103, the payload at 30 (header) + 17 (fname)
  // bytes later. We start clobbering at offset=150, so the header is intanct
  // but decompression fails.
  memset(&content[150], 0, 40);
  ASSERT_OK(
      zr.Parse(TraceBlobView(TraceBlob::CopyFrom(content, sizeof(kTestZip)))));
  ASSERT_EQ(zr.files().size(), 2u);
  std::vector<uint8_t> ignored;
  ASSERT_OK(zr.files()[0].Decompress(&ignored));
  ASSERT_THAT(zr.files()[1].Decompress(&ignored), IsError());
}

#endif  // PERFETTO_BUILDFLAG(PERFETTO_ZLIB)

}  // namespace
}  // namespace perfetto::trace_processor::util
