// Copyright 2016 The Bazel Authors. All rights reserved.
//
// 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/tools/singlejar/zip_headers.h"

#include "googletest/include/gtest/gtest.h"

namespace {

const uint8_t kPoison = 0xFB;

TEST(ZipHeadersTest, LocalHeader) {
  uint8_t bytes[256];
  memset(bytes, kPoison, sizeof(bytes));
  LH* lh = reinterpret_cast<LH*>(bytes);

  // Simple fields.
  lh->signature();
  EXPECT_TRUE(lh->is());
  lh->version(123);
  EXPECT_EQ(123, lh->version());
  lh->bit_flag(0xCAFE);
  EXPECT_EQ(0xCAFE, lh->bit_flag());
  lh->compression_method(8);
  EXPECT_EQ(8, lh->compression_method());
  lh->last_mod_file_time(0xBACD);
  EXPECT_EQ(0xBACD, lh->last_mod_file_time());
  lh->last_mod_file_date(0xCDEF);
  EXPECT_EQ(0xCDEF, lh->last_mod_file_date());
  lh->crc32(0xEF015423);
  EXPECT_EQ(0xEF015423, lh->crc32());
  lh->compressed_file_size32(1234);
  EXPECT_EQ(1234UL, lh->compressed_file_size32());
  EXPECT_EQ(1234UL, lh->compressed_file_size());
  lh->uncompressed_file_size32(3421);
  EXPECT_EQ(3421UL, lh->uncompressed_file_size32());
  EXPECT_EQ(3421UL, lh->uncompressed_file_size());
  lh->file_name("foobar", 6);
  EXPECT_EQ(6UL, lh->file_name_length());
  EXPECT_EQ(0, strncmp("foobar", lh->file_name(), 6));
  EXPECT_EQ("foobar", lh->file_name_string());
  EXPECT_TRUE(lh->file_name_is("foobar"));
  lh->extra_fields(nullptr, 0);
  EXPECT_EQ(0UL, lh->extra_fields_length());
  EXPECT_EQ(30UL + 6UL, lh->size());

  // Extra fields should not be written yet
  EXPECT_EQ(kPoison, *lh->extra_fields());

  // Now copy extra fields and check we can locate them.
  // The array extra_data contains two extra fields: a 'UT' field with Unix time
  // attributes, and a Zip64 extension with two uint64 values: uncompressed file
  // size 5000000000 (0x12A05F200) and compressed file size 3000000(0x2DC6C0).
  uint8_t extra_data[] = {
    // 'UT' field: 9 bytes of payload.
    'U', 'T', 9, 0,    // tag 0x5455, length 0x0009
        0x03, 0x85, 0x0a, 0x91, 0x57, 0x7d, 0x0a, 0x91, 0x57,
    // Zip64 extension: 16 bytes of payload.
    1, 0, 16, 0,       // tag 0x0001, length 0x0010
    0, 0xf2, 0x5, 0x2a, 1, 0, 0, 0,
    0xc0, 0xc6, 0x2d, 0, 0, 0, 0, 0,
  };
  lh->extra_fields(extra_data, sizeof(extra_data));
  EXPECT_EQ(sizeof(extra_data), lh->extra_fields_length());
  EXPECT_EQ(36 + sizeof(extra_data), lh->size());
  const Zip64ExtraField* zip64_field = lh->zip64_extra_field();
  ASSERT_NE(nullptr, zip64_field);
  EXPECT_EQ(16, zip64_field->payload_size());
  EXPECT_EQ(20, zip64_field->size());
  EXPECT_EQ(5000000000UL, zip64_field->attr64(0));
  EXPECT_EQ(3000000UL, zip64_field->attr64(1));

  const UnixTimeExtraField* ut_extra_field = lh->unix_time_extra_field();
  ASSERT_NE(nullptr, ut_extra_field);
  EXPECT_EQ(9, ut_extra_field->payload_size());
  EXPECT_EQ(13, ut_extra_field->size());
  EXPECT_EQ(2, ut_extra_field->timestamp_count());
  EXPECT_TRUE(ut_extra_field->has_modification_time());
  EXPECT_TRUE(ut_extra_field->has_access_time());
  EXPECT_FALSE(ut_extra_field->has_creation_time());

  // Check that 64-bit sizes are returned correctly.
  lh->compressed_file_size32(0xFFFFFFFF);
  lh->uncompressed_file_size32(0xFFFFFFFF);
  EXPECT_EQ(3000000UL, lh->compressed_file_size());
  EXPECT_EQ(5000000000UL, lh->uncompressed_file_size());
  EXPECT_EQ(3000000UL, lh->in_zip_size());

  // Data hasn't been written:
  EXPECT_EQ(kPoison, *lh->data());
}

TEST(ZipHeadersTest, CentralDirectoryHeader) {
  uint8_t bytes[256];
  memset(bytes, kPoison, sizeof(bytes));
  CDH* cdh = reinterpret_cast<CDH*>(bytes);

  // Simple fields.
  cdh->signature();
  EXPECT_TRUE(cdh->is());
  cdh->version(123);
  EXPECT_EQ(123, cdh->version());
  cdh->version_to_extract(321);
  EXPECT_EQ(321, cdh->version_to_extract());
  cdh->bit_flag(0xCAFE);
  EXPECT_EQ(0xCAFE, cdh->bit_flag());
  cdh->compression_method(8);
  EXPECT_EQ(8, cdh->compression_method());
  cdh->last_mod_file_time(0xBACD);
  EXPECT_EQ(0xBACD, cdh->last_mod_file_time());
  cdh->last_mod_file_date(0xCDEF);
  EXPECT_EQ(0xCDEF, cdh->last_mod_file_date());
  cdh->crc32(0xEF015423);
  EXPECT_EQ(0xEF015423, cdh->crc32());
  cdh->compressed_file_size32(1234);
  EXPECT_EQ(1234U, cdh->compressed_file_size32());
  EXPECT_EQ(1234UL, cdh->compressed_file_size());
  cdh->uncompressed_file_size32(3421);
  EXPECT_EQ(3421U, cdh->uncompressed_file_size32());
  EXPECT_EQ(3421UL, cdh->uncompressed_file_size());
  cdh->file_name("foobar", 6);
  EXPECT_EQ(6, cdh->file_name_length());
  EXPECT_EQ(0, strncmp("foobar", cdh->file_name(), 6));
  EXPECT_EQ("foobar", cdh->file_name_string());
  EXPECT_TRUE(cdh->file_name_is("foobar"));
  cdh->extra_fields(nullptr, 0);
  EXPECT_EQ(0, cdh->extra_fields_length());
  cdh->comment_length(0);
  EXPECT_EQ(0, cdh->comment_length());
  cdh->start_disk_nr(42);
  EXPECT_EQ(42, cdh->start_disk_nr());
  cdh->internal_attributes(1932);
  EXPECT_EQ(1932, cdh->internal_attributes());
  cdh->external_attributes(1234567);
  EXPECT_EQ(1234567UL, cdh->external_attributes());
  cdh->local_header_offset32(76234);
  EXPECT_EQ(76234U, cdh->local_header_offset32());
  EXPECT_EQ(76234UL, cdh->local_header_offset());

  // Only one variable field (filename) is present:
  EXPECT_EQ(46UL + 6UL, cdh->size());

  // Extra fields should not be written yet
  EXPECT_EQ(kPoison, *cdh->extra_fields());

  // Now copy extra fields and check we can locate them.
  // The array extra_data contains two extra fields: a 'UT' field with Unix time
  // attributes, and a Zip64 extension with two uint64 values: original file
  // size 5000000000 (0x12A05F200) and compressed file size 3000000(0x2DC6C0).
  uint8_t extra_data[] = {
    // 'UT' field: 5 bytes of payload (only mod time, reagrdless of flag bits)
    'U', 'T', 5, 0, 0x03, 0x85, 0x0a, 0x91, 0x57,
    // Zip64 extension: 16 bytes of payload.
    1, 0, 16, 0,    // tag 0x0001, length 0x0010
    0, 0xf2, 0x5, 0x2a, 1, 0, 0, 0,   // 0x12a05f200 = 5000000000
    0xc0, 0xc6, 0x2d, 0, 0, 0, 0, 0,  // 0x2dc6c0 = 3000000
  };
  cdh->extra_fields(extra_data, sizeof(extra_data));
  EXPECT_EQ(sizeof(extra_data), cdh->extra_fields_length());
  EXPECT_EQ(52 + sizeof(extra_data), cdh->size());
  const Zip64ExtraField* zip64_field = cdh->zip64_extra_field();
  ASSERT_NE(nullptr, zip64_field);
  EXPECT_EQ(16, zip64_field->payload_size());
  EXPECT_EQ(20, zip64_field->size());
  EXPECT_EQ(5000000000UL, zip64_field->attr64(0));
  EXPECT_EQ(3000000UL, zip64_field->attr64(1));
  const UnixTimeExtraField* ut_extra_field = cdh->unix_time_extra_field();
  ASSERT_NE(nullptr, ut_extra_field);
  EXPECT_EQ(5, ut_extra_field->payload_size());
  EXPECT_EQ(9, ut_extra_field->size());
  EXPECT_EQ(1, ut_extra_field->timestamp_count());
  EXPECT_TRUE(ut_extra_field->has_modification_time());
  EXPECT_EQ(0x57910A85UL, ut_extra_field->timestamp(0));

  // Check that 64-bit sizes are returned correctly.
  cdh->compressed_file_size32(0xFFFFFFFF);
  cdh->uncompressed_file_size32(0xFFFFFFFF);
  EXPECT_EQ(3000000UL, cdh->compressed_file_size());
  EXPECT_EQ(5000000000UL, cdh->uncompressed_file_size());

  // Check that a file with 32-bit compressed size, 64-bit original size
  // and 64-bit local header offset is handled correctly. Zip64 extension
  // field is this case contains two 64-bit quantities, original file size
  // and offset.
  uint8_t extra_data2[] = {
      1, 0,    16,   0,                 // tag 0x0001, length 0x0010
      0, 0xf2, 0x5,  0x2a, 1, 0, 0, 0,  // 0x12a05f200 = 5000000000
      0, 0xbc, 0xa0, 0x65, 1, 0, 0, 0,  // 0x165A0BC00 = 6000000000
  };
  cdh->extra_fields(extra_data2, sizeof(extra_data2));
  EXPECT_EQ(sizeof(extra_data2), cdh->extra_fields_length());
  cdh->compressed_file_size32(1234);
  cdh->uncompressed_file_size32(0xFFFFFFFF);
  cdh->local_header_offset32(0xFFFFFFFF);
  EXPECT_EQ(1234UL, cdh->compressed_file_size());
  EXPECT_EQ(5000000000UL, cdh->uncompressed_file_size());
  EXPECT_EQ(6000000000UL, cdh->local_header_offset());

  // Only uncompressed file size is 64-bit quantity.
  uint8_t extra_data3[] = {
      1, 0,    8,    0,                 // tag 0x0001, length 0x0008
      0, 0xbc, 0xa0, 0x65, 1, 0, 0, 0,  // 0x165A0BC00 = 6000000000
  };
  cdh->extra_fields(extra_data3, sizeof(extra_data3));
  EXPECT_EQ(sizeof(extra_data3), cdh->extra_fields_length());
  cdh->compressed_file_size32(123);
  cdh->uncompressed_file_size32(0xFFFFFFFF);
  cdh->local_header_offset32(42);
  EXPECT_EQ(123UL, cdh->compressed_file_size());
  EXPECT_EQ(6000000000UL, cdh->uncompressed_file_size());
  EXPECT_EQ(42UL, cdh->local_header_offset());
}

TEST(ZipHeadersTest, ECD64Locator) {
  uint8_t bytes[256];
  memset(bytes, kPoison, sizeof(bytes));
  ECD64Locator* ecd64loc = reinterpret_cast<ECD64Locator*>(bytes);

  ecd64loc->signature();
  EXPECT_TRUE(ecd64loc->is());
  ecd64loc->ecd64_disk_nr(123456);
  EXPECT_EQ(123456UL, ecd64loc->ecd64_disk_nr());
  ecd64loc->ecd64_offset(6000000000);
  EXPECT_EQ(6000000000UL, ecd64loc->ecd64_offset());
  ecd64loc->total_disks(213456);
  EXPECT_EQ(213456UL, ecd64loc->total_disks());
}

TEST(ZipHeadersTest, Zip64EndOfCentralDirectory) {
  uint8_t bytes[256];
  memset(bytes, kPoison, sizeof(bytes));

  ECD64* ecd64 = reinterpret_cast<ECD64*>(bytes);
  ecd64->signature();
  EXPECT_TRUE(ecd64->is());
  ecd64->remaining_size(44);
  EXPECT_EQ(44UL, ecd64->remaining_size());
  ecd64->version(56);
  EXPECT_EQ(56UL, ecd64->version());
  ecd64->version_to_extract(754);
  EXPECT_EQ(754UL, ecd64->version_to_extract());
  ecd64->this_disk_nr(75123);
  EXPECT_EQ(75123UL, ecd64->this_disk_nr());
  ecd64->cen_disk_nr(87654);
  EXPECT_EQ(87654UL, ecd64->cen_disk_nr());
  ecd64->this_disk_entries(9000000000);
  EXPECT_EQ(9000000000UL, ecd64->this_disk_entries());
  ecd64->total_entries(8000000000);
  EXPECT_EQ(8000000000UL, ecd64->total_entries());
  ecd64->cen_size(19000000000);
  EXPECT_EQ(19000000000UL, ecd64->cen_size());
  ecd64->cen_offset(11000000000);
  EXPECT_EQ(11000000000UL, ecd64->cen_offset());
}

TEST(ZipHeadersTest, EndOfCentralDirectory) {
  uint8_t bytes[256];
  memset(bytes, kPoison, sizeof(bytes));
  ECD64Locator* ecd64loc = reinterpret_cast<ECD64Locator*>(bytes);
  ECD* ecd = reinterpret_cast<ECD*>(bytes + sizeof(ECD64Locator));

  ecd->signature();
  EXPECT_TRUE(ecd->is());
  ecd->this_disk_nr(123);
  EXPECT_EQ(123, ecd->this_disk_nr());
  ecd->cen_disk_nr(4123);
  EXPECT_EQ(4123, ecd->cen_disk_nr());
  ecd->this_disk_entries16(23);
  EXPECT_EQ(23, ecd->this_disk_entries16());
  ecd->total_entries16(23);
  EXPECT_EQ(23, ecd->total_entries16());
  ecd->cen_size32(123400);
  EXPECT_EQ(123400U, ecd->cen_size32());
  ecd->cen_offset32(123000);
  EXPECT_EQ(123000U, ecd->cen_offset32());
  uint8_t comment_bytes[] = {0xCA, 0xFE};
  ecd->comment(comment_bytes, sizeof(comment_bytes));
  EXPECT_EQ(sizeof(comment_bytes), ecd->comment_length());
  EXPECT_EQ(comment_bytes[0], ecd->comment()[0]);
  EXPECT_EQ(comment_bytes[1], ecd->comment()[1]);

  // ECD64 locator has not been constructed yet, so:
  EXPECT_EQ(0xFFFFFFFFFFFFFFFFLL, ecd->ecd64_offset());

  // Now construct it and see it used:
  ecd64loc->signature();
  ecd64loc->ecd64_offset(9876543210);
  EXPECT_EQ(9876543210UL, ecd->ecd64_offset());
}

TEST(ZipHeadersTest, Zip64ExtraFieldTest) {
  uint8_t bytes[256];
  memset(bytes, kPoison, sizeof(bytes));
  Zip64ExtraField* z64 = reinterpret_cast<Zip64ExtraField*>(bytes);

  z64->signature();
  EXPECT_TRUE(z64->is());
  z64->payload_size(16);
  z64->attr64(0, 9876543210);
  EXPECT_EQ(9876543210UL, z64->attr64(0));
  z64->attr64(1, 8976543210);
  EXPECT_EQ(8976543210UL, z64->attr64(1));
  EXPECT_EQ(kPoison, bytes[z64->size()]);
}

}  // namespace
