/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "CTSerialization.h"
#include "CTTestUtils.h"
#include "gtest/gtest.h"
#include "mozilla/Move.h"

namespace mozilla { namespace ct {

using namespace pkix;

class CTSerializationTest : public ::testing::Test
{
public:
  void SetUp() override
  {
    mTestDigitallySigned = GetTestDigitallySigned();
    mTestSignatureData = GetTestDigitallySignedData();
  }

protected:
  Buffer mTestDigitallySigned;
  Buffer mTestSignatureData;
};

TEST_F(CTSerializationTest, DecodesDigitallySigned)
{
  Input digitallySigned = InputForBuffer(mTestDigitallySigned);
  Reader digitallySignedReader(digitallySigned);

  DigitallySigned parsed;
  ASSERT_EQ(Success,
    DecodeDigitallySigned(digitallySignedReader, parsed));
  EXPECT_TRUE(digitallySignedReader.AtEnd());

  EXPECT_EQ(DigitallySigned::HashAlgorithm::SHA256,
            parsed.hashAlgorithm);
  EXPECT_EQ(DigitallySigned::SignatureAlgorithm::ECDSA,
            parsed.signatureAlgorithm);
  EXPECT_EQ(mTestSignatureData, parsed.signatureData);
}

TEST_F(CTSerializationTest, FailsToDecodePartialDigitallySigned)
{
  Input partial;
  ASSERT_EQ(Success,
    partial.Init(mTestDigitallySigned.begin(),
      mTestDigitallySigned.length() - 5));
  Reader partialReader(partial);

  DigitallySigned parsed;

  EXPECT_NE(Success, DecodeDigitallySigned(partialReader, parsed));
}

TEST_F(CTSerializationTest, EncodesDigitallySigned)
{
  DigitallySigned digitallySigned;
  digitallySigned.hashAlgorithm =
    DigitallySigned::HashAlgorithm::SHA256;
  digitallySigned.signatureAlgorithm =
    DigitallySigned::SignatureAlgorithm::ECDSA;
  digitallySigned.signatureData = cloneBuffer(mTestSignatureData);

  Buffer encoded;

  ASSERT_EQ(Success, EncodeDigitallySigned(digitallySigned, encoded));
  EXPECT_EQ(mTestDigitallySigned, encoded);
}

TEST_F(CTSerializationTest, EncodesLogEntryForX509Cert)
{
  LogEntry entry;
  GetX509CertLogEntry(entry);

  Buffer encoded;
  ASSERT_EQ(Success, EncodeLogEntry(entry, encoded));
  EXPECT_EQ((718U + 5U), encoded.length());
  // First two bytes are log entry type. Next, length:
  // Length is 718 which is 512 + 206, which is 0x2ce
  Buffer expectedPrefix;
  MOZ_RELEASE_ASSERT(expectedPrefix.append("\0\0\0\x2\xCE", 5));
  Buffer encodedPrefix;
  MOZ_RELEASE_ASSERT(encodedPrefix.
    append(encoded.begin(), encoded.begin() + 5));
  EXPECT_EQ(expectedPrefix, encodedPrefix);
}

TEST_F(CTSerializationTest, EncodesLogEntryForPrecert)
{
  LogEntry entry;
  GetPrecertLogEntry(entry);

  Buffer encoded;
  ASSERT_EQ(Success, EncodeLogEntry(entry, encoded));
  // log entry type + issuer key + length + tbsCertificate
  EXPECT_EQ((2U + 32U + 3U + entry.tbsCertificate.length()), encoded.length());

  // First two bytes are log entry type.
  Buffer expectedPrefix;
  MOZ_RELEASE_ASSERT(expectedPrefix.append("\0\x1", 2));
  Buffer encodedPrefix;
  MOZ_RELEASE_ASSERT(encodedPrefix.
    append(encoded.begin(), encoded.begin() + 2));
  EXPECT_EQ(expectedPrefix, encodedPrefix);

  // Next is the issuer key (32 bytes).
  Buffer encodedKeyHash;
  MOZ_RELEASE_ASSERT(encodedKeyHash.
    append(encoded.begin() + 2, encoded.begin() + 2 + 32));
  EXPECT_EQ(GetDefaultIssuerKeyHash(), encodedKeyHash);
}

TEST_F(CTSerializationTest, EncodesV1SCTSignedData)
{
  uint64_t timestamp = UINT64_C(0x139fe353cf5);
  const uint8_t DUMMY_BYTES[] = { 0x61, 0x62, 0x63 }; // abc
  Input dummyEntry(DUMMY_BYTES);
  Input emptyExtensions;
  Buffer encoded;
  ASSERT_EQ(Success, EncodeV1SCTSignedData(
    timestamp, dummyEntry, emptyExtensions, encoded));
  EXPECT_EQ((size_t) 15, encoded.length());

  const uint8_t EXPECTED_BYTES[] = {
    0x00, // version
    0x00, // signature type
    0x00, 0x00, 0x01, 0x39, 0xFE, 0x35, 0x3C, 0xF5, // timestamp
    0x61, 0x62, 0x63, // log signature
    0x00, 0x00 // extensions (empty)
  };
  Buffer expectedBuffer;
  MOZ_RELEASE_ASSERT(
    expectedBuffer.append(EXPECTED_BYTES, sizeof(EXPECTED_BYTES)));
  EXPECT_EQ(expectedBuffer, encoded);
}

TEST_F(CTSerializationTest, DecodesSCTList)
{
  // Two items in the list: "abc", "def"
  const uint8_t ENCODED[] = {
    0x00, 0x0a, 0x00, 0x03, 0x61, 0x62, 0x63, 0x00, 0x03, 0x64, 0x65, 0x66
  };
  const uint8_t DECODED_1[] = { 0x61, 0x62, 0x63 };
  const uint8_t DECODED_2[] = { 0x64, 0x65, 0x66 };

  Reader listReader;
  ASSERT_EQ(Success, DecodeSCTList(Input(ENCODED), listReader));

  Input decoded1;
  ASSERT_EQ(Success, ReadSCTListItem(listReader, decoded1));

  Input decoded2;
  ASSERT_EQ(Success, ReadSCTListItem(listReader, decoded2));

  EXPECT_TRUE(listReader.AtEnd());
  EXPECT_TRUE(InputsAreEqual(decoded1, Input(DECODED_1)));
  EXPECT_TRUE(InputsAreEqual(decoded2, Input(DECODED_2)));
}

TEST_F(CTSerializationTest, FailsDecodingInvalidSCTList)
{
  // A list with one item that's too short (the second one)
  const uint8_t ENCODED[] = {
    0x00, 0x0a, 0x00, 0x03, 0x61, 0x62, 0x63, 0x00, 0x05, 0x64, 0x65, 0x66
  };

  Reader listReader;
  ASSERT_EQ(Success, DecodeSCTList(Input(ENCODED), listReader));
  Input decoded1;
  EXPECT_EQ(Success, ReadSCTListItem(listReader, decoded1));
  Input decoded2;
  EXPECT_NE(Success, ReadSCTListItem(listReader, decoded2));
}

TEST_F(CTSerializationTest, EncodesSCTList)
{
  const uint8_t SCT_1[] = { 0x61, 0x62, 0x63 };
  const uint8_t SCT_2[] = { 0x64, 0x65, 0x66 };

  Vector<Input> list;
  ASSERT_TRUE(list.append(Move(Input(SCT_1))));
  ASSERT_TRUE(list.append(Move(Input(SCT_2))));

  Buffer encodedList;
  ASSERT_EQ(Success, EncodeSCTList(list, encodedList));

  Reader listReader;
  ASSERT_EQ(Success, DecodeSCTList(InputForBuffer(encodedList), listReader));

  Input decoded1;
  ASSERT_EQ(Success, ReadSCTListItem(listReader, decoded1));
  EXPECT_TRUE(InputsAreEqual(decoded1, Input(SCT_1)));

  Input decoded2;
  ASSERT_EQ(Success, ReadSCTListItem(listReader, decoded2));
  EXPECT_TRUE(InputsAreEqual(decoded2, Input(SCT_2)));

  EXPECT_TRUE(listReader.AtEnd());
}

TEST_F(CTSerializationTest, DecodesSignedCertificateTimestamp)
{
  Buffer encodedSctBuffer = GetTestSignedCertificateTimestamp();
  Input encodedSctInput = InputForBuffer(encodedSctBuffer);
  Reader encodedSctReader(encodedSctInput);

  SignedCertificateTimestamp sct;
  ASSERT_EQ(Success,
    DecodeSignedCertificateTimestamp(encodedSctReader, sct));
  EXPECT_EQ(SignedCertificateTimestamp::Version::V1, sct.version);
  EXPECT_EQ(GetTestPublicKeyId(), sct.logId);
  const uint64_t expectedTime = 1365181456089;
  EXPECT_EQ(expectedTime, sct.timestamp);
  const size_t expectedSignatureLength = 71;
  EXPECT_EQ(expectedSignatureLength, sct.signature.signatureData.length());
  EXPECT_TRUE(sct.extensions.empty());
}

TEST_F(CTSerializationTest, FailsDecodingInvalidSignedCertificateTimestamp)
{
  SignedCertificateTimestamp sct;

  // Invalid version
  const uint8_t INVALID_VERSION_BYTES[] = { 0x02, 0x00 };
  Input invalidVersionSctInput(INVALID_VERSION_BYTES);
  Reader invalidVersionSctReader(invalidVersionSctInput);
  EXPECT_EQ(Result::ERROR_BAD_DER,
    DecodeSignedCertificateTimestamp(invalidVersionSctReader, sct));

  // Valid version, invalid length (missing data)
  const uint8_t INVALID_LENGTH_BYTES[] = { 0x00, 0x0a, 0x0b, 0x0c };
  Input invalidLengthSctInput(INVALID_LENGTH_BYTES);
  Reader invalidLengthSctReader(invalidLengthSctInput);
  EXPECT_EQ(Result::ERROR_BAD_DER,
    DecodeSignedCertificateTimestamp(invalidLengthSctReader, sct));
}

TEST_F(CTSerializationTest, EncodesValidSignedTreeHead)
{
  SignedTreeHead signedTreeHead;
  GetSampleSignedTreeHead(signedTreeHead);

  Buffer encoded;
  ASSERT_EQ(Success,
    EncodeTreeHeadSignature(signedTreeHead, encoded));
  // Expected size is 50 bytes:
  // Byte 0 is version, byte 1 is signature type
  // Bytes 2-9 are timestamp
  // Bytes 10-17 are tree size
  // Bytes 18-49 are sha256 root hash
  ASSERT_EQ(50u, encoded.length());
  const uint8_t EXPECTED_BYTES_PREFIX[] = {
    0x00, // version
    0x01, // signature type
    0x00, 0x00, 0x01, 0x45, 0x3c, 0x5f, 0xb8, 0x35, // timestamp
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15  // tree size
    // sha256 root hash should follow
  };
  Buffer expectedBuffer;
  MOZ_RELEASE_ASSERT(expectedBuffer.append(EXPECTED_BYTES_PREFIX, 18));
  Buffer hash = GetSampleSTHSHA256RootHash();
  MOZ_RELEASE_ASSERT(expectedBuffer.append(hash.begin(), hash.length()));
  EXPECT_EQ(expectedBuffer, encoded);
}

} }  // namespace mozilla::ct