/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=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 <cstring>

#include "gtest/gtest.h"
#include "scoped_ptrs_util.h"
#include "secitem.h"
#include "secport.h"

namespace nss_test {

// ---------------------------------------------------------------------------
// SECITEM_AllocItem / SECITEM_MakeItem
// ---------------------------------------------------------------------------

class SecItemAllocTest : public ::testing::Test {};

// AllocItem with NULL item: allocates the SECItem struct and its data buffer.
TEST_F(SecItemAllocTest, AllocItemHeapNullItem) {
  ScopedSECItem item(SECITEM_AllocItem(nullptr, nullptr, 10));
  ASSERT_TRUE(item);
  EXPECT_EQ(10U, item->len);
  EXPECT_TRUE(item->data);
}

// AllocItem with an existing item: allocates only the data buffer.
TEST_F(SecItemAllocTest, AllocItemHeapExistingItem) {
  SECItem item = {siBuffer, nullptr, 0};
  SECItem *result = SECITEM_AllocItem(nullptr, &item, 8);
  ASSERT_EQ(&item, result);
  EXPECT_EQ(8U, item.len);
  EXPECT_TRUE(item.data);
  SECITEM_FreeItem(&item, PR_FALSE);
}

// len=0: data buffer is not allocated; data is NULL and len is 0.
TEST_F(SecItemAllocTest, AllocItemZeroLen) {
  ScopedSECItem item(SECITEM_AllocItem(nullptr, nullptr, 0));
  ASSERT_TRUE(item);
  EXPECT_EQ(0U, item->len);
  EXPECT_FALSE(item->data);
}

// Arena alloc with NULL item: struct and data come from the arena.
TEST_F(SecItemAllocTest, AllocItemArenaNullItem) {
  ScopedPLArenaPool arena(PORT_NewArena(4096));
  ASSERT_TRUE(arena);
  SECItem *item = SECITEM_AllocItem(arena.get(), nullptr, 12);
  ASSERT_TRUE(item);
  EXPECT_EQ(12U, item->len);
  EXPECT_TRUE(item->data);
  // item and item->data live in the arena; freed when the arena is freed.
}

// Arena alloc with existing item: only data comes from the arena.
TEST_F(SecItemAllocTest, AllocItemArenaExistingItem) {
  ScopedPLArenaPool arena(PORT_NewArena(4096));
  ASSERT_TRUE(arena);
  SECItem item = {siBuffer, nullptr, 0};
  SECItem *result = SECITEM_AllocItem(arena.get(), &item, 7);
  ASSERT_EQ(&item, result);
  EXPECT_EQ(7U, item.len);
  EXPECT_TRUE(item.data);
}

// MakeItem: copies data, result type is always siBuffer.
TEST_F(SecItemAllocTest, MakeItemHeap) {
  const uint8_t data[] = {0x01, 0x02, 0x03};
  StackSECItem dest;
  ASSERT_EQ(SECSuccess, SECITEM_MakeItem(nullptr, &dest, data, sizeof(data)));
  EXPECT_EQ(siBuffer, dest.type);
  ASSERT_EQ(3U, dest.len);
  EXPECT_EQ(0, memcmp(dest.data, data, sizeof(data)));
}

TEST_F(SecItemAllocTest, MakeItemArena) {
  ScopedPLArenaPool arena(PORT_NewArena(4096));
  ASSERT_TRUE(arena);
  const uint8_t data[] = {0xDE, 0xAD};
  SECItem dest = {siBuffer, nullptr, 0};
  ASSERT_EQ(SECSuccess,
            SECITEM_MakeItem(arena.get(), &dest, data, sizeof(data)));
  EXPECT_EQ(siBuffer, dest.type);
  EXPECT_EQ(2U, dest.len);
  EXPECT_EQ(0, memcmp(dest.data, data, sizeof(data)));
}

// MakeItem with zero length: succeeds, data is NULL, len is 0.
TEST_F(SecItemAllocTest, MakeItemZeroLen) {
  StackSECItem dest;
  ASSERT_EQ(SECSuccess, SECITEM_MakeItem(nullptr, &dest, nullptr, 0));
  EXPECT_EQ(0U, dest.len);
  EXPECT_FALSE(dest.data);
}

// ---------------------------------------------------------------------------
// SECITEM_ReallocItemV2
// ---------------------------------------------------------------------------

class SecItemReallocTest : public ::testing::Test {};

// Grow an item that starts with no allocation.
TEST_F(SecItemReallocTest, ReallocV2GrowHeap) {
  StackSECItem item;
  ASSERT_EQ(SECSuccess, SECITEM_ReallocItemV2(nullptr, &item, 4));
  EXPECT_EQ(4U, item.len);
  EXPECT_TRUE(item.data);
  ASSERT_EQ(SECSuccess, SECITEM_ReallocItemV2(nullptr, &item, 16));
  EXPECT_EQ(16U, item.len);
  EXPECT_TRUE(item.data);
}

TEST_F(SecItemReallocTest, ReallocV2ShrinkHeap) {
  StackSECItem item;
  ASSERT_EQ(SECSuccess, SECITEM_ReallocItemV2(nullptr, &item, 16));
  ASSERT_EQ(SECSuccess, SECITEM_ReallocItemV2(nullptr, &item, 4));
  EXPECT_EQ(4U, item.len);
  EXPECT_TRUE(item.data);
}

// Shrink in an arena: reuses the existing block, just decrements len.
TEST_F(SecItemReallocTest, ReallocV2ShrinkArena) {
  ScopedPLArenaPool arena(PORT_NewArena(4096));
  ASSERT_TRUE(arena);
  SECItem item = {siBuffer, nullptr, 0};
  ASSERT_EQ(SECSuccess, SECITEM_ReallocItemV2(arena.get(), &item, 16));
  unsigned char *orig_ptr = item.data;
  ASSERT_EQ(SECSuccess, SECITEM_ReallocItemV2(arena.get(), &item, 4));
  EXPECT_EQ(4U, item.len);
  EXPECT_EQ(orig_ptr, item.data);  // still the same block
}

TEST_F(SecItemReallocTest, ReallocV2GrowArena) {
  ScopedPLArenaPool arena(PORT_NewArena(4096));
  ASSERT_TRUE(arena);
  SECItem item = {siBuffer, nullptr, 0};
  ASSERT_EQ(SECSuccess, SECITEM_ReallocItemV2(arena.get(), &item, 8));
  ASSERT_EQ(SECSuccess, SECITEM_ReallocItemV2(arena.get(), &item, 24));
  EXPECT_EQ(24U, item.len);
  EXPECT_TRUE(item.data);
}

// item->data == NULL: treated as a fresh allocation.
TEST_F(SecItemReallocTest, ReallocV2FromNull) {
  StackSECItem item;
  ASSERT_EQ(SECSuccess, SECITEM_ReallocItemV2(nullptr, &item, 8));
  EXPECT_EQ(8U, item.len);
  EXPECT_TRUE(item.data);
}

// newlen == 0: frees data and zeroes the item fields.
TEST_F(SecItemReallocTest, ReallocV2ToZero) {
  StackSECItem item;
  ASSERT_EQ(SECSuccess, SECITEM_ReallocItemV2(nullptr, &item, 8));
  ASSERT_EQ(SECSuccess, SECITEM_ReallocItemV2(nullptr, &item, 0));
  EXPECT_EQ(0U, item.len);
  EXPECT_FALSE(item.data);
  // StackSECItem dtor will call SECITEM_FreeItem with data==nullptr — safe.
}

// newlen == current len: no-op, pointer is unchanged.
TEST_F(SecItemReallocTest, ReallocV2SameSizeNoOp) {
  StackSECItem item;
  ASSERT_EQ(SECSuccess, SECITEM_ReallocItemV2(nullptr, &item, 8));
  unsigned char *ptr = item.data;
  ASSERT_EQ(SECSuccess, SECITEM_ReallocItemV2(nullptr, &item, 8));
  EXPECT_EQ(8U, item.len);
  EXPECT_EQ(ptr, item.data);
}

// ---------------------------------------------------------------------------
// SECITEM_ReallocItem (deprecated)
// ---------------------------------------------------------------------------

class SecItemReallocLegacyTest : public ::testing::Test {};

// oldlen=0, newlen=0: degenerate no-op, returns SECSuccess.
TEST_F(SecItemReallocLegacyTest, ReallocLegacyOldlenZeroNewlenZero) {
  SECItem item = {siBuffer, nullptr, 0};
  EXPECT_EQ(SECSuccess, SECITEM_ReallocItem(nullptr, &item, 0, 0));
}

// oldlen=0: allocates fresh data buffer (note: legacy bug — len is updated
// here).
TEST_F(SecItemReallocLegacyTest, ReallocLegacyOldlenZeroNewlenNonzero) {
  SECItem item = {siBuffer, nullptr, 0};
  ASSERT_EQ(SECSuccess, SECITEM_ReallocItem(nullptr, &item, 0, 8));
  EXPECT_TRUE(item.data);
  SECITEM_FreeItem(&item, PR_FALSE);
}

// oldlen>0: reallocates existing data buffer on the heap.
TEST_F(SecItemReallocLegacyTest, ReallocLegacyGrowHeap) {
  SECItem item = {siBuffer, nullptr, 0};
  ASSERT_EQ(SECSuccess, SECITEM_ReallocItem(nullptr, &item, 0, 4));
  ASSERT_EQ(SECSuccess, SECITEM_ReallocItem(nullptr, &item, 4, 16));
  EXPECT_TRUE(item.data);
  // Note: legacy bug — item.len is still 4, not 16.
  SECITEM_FreeItem(&item, PR_FALSE);
}

// oldlen=0, arena: allocates from arena.
TEST_F(SecItemReallocLegacyTest, ReallocLegacyArena) {
  ScopedPLArenaPool arena(PORT_NewArena(4096));
  ASSERT_TRUE(arena);
  SECItem item = {siBuffer, nullptr, 0};
  ASSERT_EQ(SECSuccess, SECITEM_ReallocItem(arena.get(), &item, 0, 8));
  EXPECT_TRUE(item.data);
}

// oldlen>0, arena: grows the existing arena block.
TEST_F(SecItemReallocLegacyTest, ReallocLegacyArenaGrow) {
  ScopedPLArenaPool arena(PORT_NewArena(4096));
  ASSERT_TRUE(arena);
  SECItem item = {siBuffer, nullptr, 0};
  ASSERT_EQ(SECSuccess, SECITEM_ReallocItem(arena.get(), &item, 0, 4));
  ASSERT_EQ(SECSuccess, SECITEM_ReallocItem(arena.get(), &item, 4, 16));
  EXPECT_TRUE(item.data);
}

// ---------------------------------------------------------------------------
// SECITEM_CompareItem / SECITEM_ItemsAreEqual
// ---------------------------------------------------------------------------

class SecItemCompareTest : public ::testing::Test {};

TEST_F(SecItemCompareTest, CompareEqualItems) {
  const uint8_t data[] = {1, 2, 3};
  SECItem a = {siBuffer, const_cast<uint8_t *>(data), sizeof(data)};
  SECItem b = {siBuffer, const_cast<uint8_t *>(data), sizeof(data)};
  EXPECT_EQ(SECEqual, SECITEM_CompareItem(&a, &b));
}

// Same pointer: short-circuits to SECEqual without touching data.
TEST_F(SecItemCompareTest, CompareSamePointer) {
  const uint8_t data[] = {1, 2, 3};
  SECItem a = {siBuffer, const_cast<uint8_t *>(data), sizeof(data)};
  EXPECT_EQ(SECEqual, SECITEM_CompareItem(&a, &a));
}

TEST_F(SecItemCompareTest, CompareBothEmpty) {
  SECItem a = {siBuffer, nullptr, 0};
  SECItem b = {siBuffer, nullptr, 0};
  EXPECT_EQ(SECEqual, SECITEM_CompareItem(&a, &b));
}

TEST_F(SecItemCompareTest, CompareNullItemA) {
  const uint8_t data[] = {1};
  SECItem b = {siBuffer, const_cast<uint8_t *>(data), sizeof(data)};
  EXPECT_EQ(SECLessThan, SECITEM_CompareItem(nullptr, &b));
}

TEST_F(SecItemCompareTest, CompareNullItemB) {
  const uint8_t data[] = {1};
  SECItem a = {siBuffer, const_cast<uint8_t *>(data), sizeof(data)};
  EXPECT_EQ(SECGreaterThan, SECITEM_CompareItem(&a, nullptr));
}

TEST_F(SecItemCompareTest, CompareLexLess) {
  const uint8_t d1[] = {1, 2, 3};
  const uint8_t d2[] = {1, 2, 4};
  SECItem a = {siBuffer, const_cast<uint8_t *>(d1), sizeof(d1)};
  SECItem b = {siBuffer, const_cast<uint8_t *>(d2), sizeof(d2)};
  EXPECT_EQ(SECLessThan, SECITEM_CompareItem(&a, &b));
}

TEST_F(SecItemCompareTest, CompareLexGreater) {
  const uint8_t d1[] = {1, 2, 4};
  const uint8_t d2[] = {1, 2, 3};
  SECItem a = {siBuffer, const_cast<uint8_t *>(d1), sizeof(d1)};
  SECItem b = {siBuffer, const_cast<uint8_t *>(d2), sizeof(d2)};
  EXPECT_EQ(SECGreaterThan, SECITEM_CompareItem(&a, &b));
}

// Same prefix, a is shorter: a < b.
TEST_F(SecItemCompareTest, CompareSamePrefixShorter) {
  const uint8_t d1[] = {1, 2};
  const uint8_t d2[] = {1, 2, 3};
  SECItem a = {siBuffer, const_cast<uint8_t *>(d1), sizeof(d1)};
  SECItem b = {siBuffer, const_cast<uint8_t *>(d2), sizeof(d2)};
  EXPECT_EQ(SECLessThan, SECITEM_CompareItem(&a, &b));
}

// Same prefix, a is longer: a > b.
TEST_F(SecItemCompareTest, CompareSamePrefixLonger) {
  const uint8_t d1[] = {1, 2, 3};
  const uint8_t d2[] = {1, 2};
  SECItem a = {siBuffer, const_cast<uint8_t *>(d1), sizeof(d1)};
  SECItem b = {siBuffer, const_cast<uint8_t *>(d2), sizeof(d2)};
  EXPECT_EQ(SECGreaterThan, SECITEM_CompareItem(&a, &b));
}

TEST_F(SecItemCompareTest, ItemsAreEqualTrue) {
  const uint8_t data[] = {0xAB, 0xCD};
  SECItem a = {siBuffer, const_cast<uint8_t *>(data), sizeof(data)};
  SECItem b = {siBuffer, const_cast<uint8_t *>(data), sizeof(data)};
  EXPECT_EQ(PR_TRUE, SECITEM_ItemsAreEqual(&a, &b));
}

TEST_F(SecItemCompareTest, ItemsAreEqualDifferentLen) {
  const uint8_t d1[] = {1, 2, 3};
  const uint8_t d2[] = {1, 2};
  SECItem a = {siBuffer, const_cast<uint8_t *>(d1), sizeof(d1)};
  SECItem b = {siBuffer, const_cast<uint8_t *>(d2), sizeof(d2)};
  EXPECT_EQ(PR_FALSE, SECITEM_ItemsAreEqual(&a, &b));
}

TEST_F(SecItemCompareTest, ItemsAreEqualSameLenDiffData) {
  const uint8_t d1[] = {1, 2, 3};
  const uint8_t d2[] = {1, 2, 4};
  SECItem a = {siBuffer, const_cast<uint8_t *>(d1), sizeof(d1)};
  SECItem b = {siBuffer, const_cast<uint8_t *>(d2), sizeof(d2)};
  EXPECT_EQ(PR_FALSE, SECITEM_ItemsAreEqual(&a, &b));
}

TEST_F(SecItemCompareTest, ItemsAreEqualBothZeroLen) {
  SECItem a = {siBuffer, nullptr, 0};
  SECItem b = {siBuffer, nullptr, 0};
  EXPECT_EQ(PR_TRUE, SECITEM_ItemsAreEqual(&a, &b));
}

// Both data pointers NULL with len==0: equal.
TEST_F(SecItemCompareTest, ItemsAreEqualBothNullData) {
  SECItem a = {siBuffer, nullptr, 0};
  SECItem b = {siBuffer, nullptr, 0};
  EXPECT_EQ(PR_TRUE, SECITEM_ItemsAreEqual(&a, &b));
}

// Same nonzero len but both data pointers NULL: equal (hits the null-data
// guard).
TEST_F(SecItemCompareTest, ItemsAreEqualBothNullDataNonzeroLen) {
  SECItem a = {siBuffer, nullptr, 3};
  SECItem b = {siBuffer, nullptr, 3};
  EXPECT_EQ(PR_TRUE, SECITEM_ItemsAreEqual(&a, &b));
}

// Same nonzero len, one data pointer NULL: not equal.
TEST_F(SecItemCompareTest, ItemsAreEqualOneNullDataNonzeroLen) {
  const uint8_t data[] = {1, 2, 3};
  SECItem a = {siBuffer, const_cast<uint8_t *>(data), 3};
  SECItem b = {siBuffer, nullptr, 3};
  EXPECT_EQ(PR_FALSE, SECITEM_ItemsAreEqual(&a, &b));
}

// ---------------------------------------------------------------------------
// SECITEM_CopyItem / SECITEM_DupItem / SECITEM_ArenaDupItem
// ---------------------------------------------------------------------------

class SecItemCopyTest : public ::testing::Test {};

// CopyItem: deep copy, original and copy are independent buffers.
TEST_F(SecItemCopyTest, CopyItemHeap) {
  const uint8_t data[] = {0xDE, 0xAD, 0xBE, 0xEF};
  SECItem from = {siBuffer, const_cast<uint8_t *>(data), sizeof(data)};
  StackSECItem to;
  ASSERT_EQ(SECSuccess, SECITEM_CopyItem(nullptr, &to, &from));
  EXPECT_EQ(siBuffer, to.type);
  ASSERT_EQ(4U, to.len);
  EXPECT_EQ(0, memcmp(to.data, data, sizeof(data)));
  EXPECT_NE(from.data, to.data);
}

TEST_F(SecItemCopyTest, CopyItemArena) {
  ScopedPLArenaPool arena(PORT_NewArena(4096));
  ASSERT_TRUE(arena);
  const uint8_t data[] = {0x01, 0x02};
  SECItem from = {siBuffer, const_cast<uint8_t *>(data), sizeof(data)};
  SECItem to = {siBuffer, nullptr, 0};
  ASSERT_EQ(SECSuccess, SECITEM_CopyItem(arena.get(), &to, &from));
  EXPECT_EQ(2U, to.len);
  EXPECT_EQ(0, memcmp(to.data, data, sizeof(data)));
}

// Source has NULL data/zero len: dest gets data=NULL, len=0.
TEST_F(SecItemCopyTest, CopyItemZeroLen) {
  SECItem from = {siBuffer, nullptr, 0};
  StackSECItem to;
  ASSERT_EQ(SECSuccess, SECITEM_CopyItem(nullptr, &to, &from));
  EXPECT_EQ(0U, to.len);
  EXPECT_FALSE(to.data);
}

// data != NULL but len == 0: treated as empty, dest gets data=NULL, len=0.
TEST_F(SecItemCopyTest, CopyItemNonNullDataZeroLen) {
  const uint8_t data[] = {0x01};
  SECItem from = {siBuffer, const_cast<uint8_t *>(data), 0};
  StackSECItem to;
  ASSERT_EQ(SECSuccess, SECITEM_CopyItem(nullptr, &to, &from));
  EXPECT_EQ(0U, to.len);
  EXPECT_FALSE(to.data);
}

// data == NULL but len != 0: succeeds and zeroes dest (documented quirk).
TEST_F(SecItemCopyTest, CopyItemNullDataNonzeroLen) {
  SECItem from = {siBuffer, nullptr, 3};
  StackSECItem to;
  ASSERT_EQ(SECSuccess, SECITEM_CopyItem(nullptr, &to, &from));
  EXPECT_EQ(0U, to.len);
  EXPECT_FALSE(to.data);
}

// Non-siBuffer type is preserved in the copy.
TEST_F(SecItemCopyTest, CopyItemTypePreserved) {
  const uint8_t data[] = {0x30};
  SECItem from = {siDEROID, const_cast<uint8_t *>(data), sizeof(data)};
  StackSECItem to;
  ASSERT_EQ(SECSuccess, SECITEM_CopyItem(nullptr, &to, &from));
  EXPECT_EQ(siDEROID, to.type);
}

// Mutating the original after a copy must not affect the copy.
TEST_F(SecItemCopyTest, CopyItemIndependence) {
  uint8_t data[] = {0x01, 0x02, 0x03};
  SECItem from = {siBuffer, data, sizeof(data)};
  StackSECItem to;
  ASSERT_EQ(SECSuccess, SECITEM_CopyItem(nullptr, &to, &from));
  data[0] = 0xFF;
  EXPECT_EQ(0x01, to.data[0]);
}

TEST_F(SecItemCopyTest, DupItemBasic) {
  const uint8_t data[] = {0xCA, 0xFE};
  SECItem from = {siBuffer, const_cast<uint8_t *>(data), sizeof(data)};
  ScopedSECItem dup(SECITEM_DupItem(&from));
  ASSERT_TRUE(dup);
  EXPECT_EQ(siBuffer, dup->type);
  ASSERT_EQ(2U, dup->len);
  EXPECT_EQ(0, memcmp(dup->data, data, sizeof(data)));
  EXPECT_NE(from.data, dup->data);
}

// NULL input: must return NULL.
TEST_F(SecItemCopyTest, DupItemNullInput) {
  EXPECT_FALSE(SECITEM_DupItem(nullptr));
}

// Zero-length source: dup has NULL data and len==0.
TEST_F(SecItemCopyTest, DupItemZeroLen) {
  SECItem from = {siBuffer, nullptr, 0};
  ScopedSECItem dup(SECITEM_DupItem(&from));
  ASSERT_TRUE(dup);
  EXPECT_EQ(0U, dup->len);
  EXPECT_FALSE(dup->data);
}

TEST_F(SecItemCopyTest, DupItemTypePreserved) {
  const uint8_t data[] = {0x30};
  SECItem from = {siDEROID, const_cast<uint8_t *>(data), sizeof(data)};
  ScopedSECItem dup(SECITEM_DupItem(&from));
  ASSERT_TRUE(dup);
  EXPECT_EQ(siDEROID, dup->type);
}

TEST_F(SecItemCopyTest, DupItemIndependence) {
  uint8_t data[] = {0x01, 0x02};
  SECItem from = {siBuffer, data, sizeof(data)};
  ScopedSECItem dup(SECITEM_DupItem(&from));
  ASSERT_TRUE(dup);
  data[0] = 0xFF;
  EXPECT_EQ(0x01, dup->data[0]);
}

// ArenaDupItem: both the struct and the data buffer come from the arena.
TEST_F(SecItemCopyTest, ArenaDupItemWithArena) {
  ScopedPLArenaPool arena(PORT_NewArena(4096));
  ASSERT_TRUE(arena);
  const uint8_t data[] = {0x11, 0x22, 0x33};
  SECItem from = {siBuffer, const_cast<uint8_t *>(data), sizeof(data)};
  SECItem *dup = SECITEM_ArenaDupItem(arena.get(), &from);
  ASSERT_TRUE(dup);
  EXPECT_EQ(3U, dup->len);
  EXPECT_EQ(0, memcmp(dup->data, data, sizeof(data)));
  // dup and dup->data are arena-owned; freed when the arena is freed.
}

// NULL arena: equivalent to DupItem.
TEST_F(SecItemCopyTest, ArenaDupItemNullArena) {
  const uint8_t data[] = {0xAB};
  SECItem from = {siBuffer, const_cast<uint8_t *>(data), sizeof(data)};
  ScopedSECItem dup(SECITEM_ArenaDupItem(nullptr, &from));
  ASSERT_TRUE(dup);
  EXPECT_EQ(1U, dup->len);
  EXPECT_EQ(0xAB, dup->data[0]);
}

// NULL input: must return NULL regardless of arena.
TEST_F(SecItemCopyTest, ArenaDupItemNullInput) {
  ScopedPLArenaPool arena(PORT_NewArena(4096));
  ASSERT_TRUE(arena);
  EXPECT_FALSE(SECITEM_ArenaDupItem(arena.get(), nullptr));
}

// ---------------------------------------------------------------------------
// SECITEM_FreeItem / SECITEM_ZfreeItem
// ---------------------------------------------------------------------------

class SecItemFreeTest : public ::testing::Test {};

// freeit=PR_TRUE: frees both the data buffer and the struct itself.
TEST_F(SecItemFreeTest, FreeItemFreeit) {
  SECItem *item = SECITEM_AllocItem(nullptr, nullptr, 8);
  ASSERT_TRUE(item);
  SECITEM_FreeItem(item, PR_TRUE);
  // item is now invalid; we just verify no crash occurred.
}

// freeit=PR_FALSE: frees only the data buffer; struct fields are zeroed.
TEST_F(SecItemFreeTest, FreeItemNoFreeit) {
  SECItem item = {siBuffer, nullptr, 0};
  ASSERT_TRUE(SECITEM_AllocItem(nullptr, &item, 8));
  SECITEM_FreeItem(&item, PR_FALSE);
  EXPECT_FALSE(item.data);
  EXPECT_EQ(0U, item.len);
}

// NULL item pointer: must not crash.
TEST_F(SecItemFreeTest, FreeItemNull) {
  SECITEM_FreeItem(nullptr, PR_FALSE);
  SECITEM_FreeItem(nullptr, PR_TRUE);
}

// ZfreeItem: zeroes then frees the data buffer; freeit=PR_TRUE frees the
// struct.
TEST_F(SecItemFreeTest, ZfreeItemFreeit) {
  SECItem *item = SECITEM_AllocItem(nullptr, nullptr, 8);
  ASSERT_TRUE(item);
  memset(item->data, 0xAA, item->len);
  SECITEM_ZfreeItem(item, PR_TRUE);
}

// ZfreeItem freeit=PR_FALSE: data zeroed and freed; struct fields cleared.
TEST_F(SecItemFreeTest, ZfreeItemNoFreeit) {
  SECItem item = {siBuffer, nullptr, 0};
  ASSERT_TRUE(SECITEM_AllocItem(nullptr, &item, 8));
  memset(item.data, 0xBB, item.len);
  SECITEM_ZfreeItem(&item, PR_FALSE);
  EXPECT_FALSE(item.data);
  EXPECT_EQ(0U, item.len);
}

// NULL item pointer: must not crash.
TEST_F(SecItemFreeTest, ZfreeItemNull) {
  SECITEM_ZfreeItem(nullptr, PR_FALSE);
  SECITEM_ZfreeItem(nullptr, PR_TRUE);
}

// ---------------------------------------------------------------------------
// SECITEM_Hash / SECITEM_HashCompare
// ---------------------------------------------------------------------------

class SecItemHashTest : public ::testing::Test {};

// Same item hashed twice produces the same result.
TEST_F(SecItemHashTest, HashConsistent) {
  const uint8_t data[] = {0x01, 0x02, 0x03, 0x04};
  SECItem item = {siBuffer, const_cast<uint8_t *>(data), sizeof(data)};
  EXPECT_EQ(SECITEM_Hash(&item), SECITEM_Hash(&item));
}

// Two distinct items produce different hash values.
TEST_F(SecItemHashTest, HashDifferentItems) {
  const uint8_t d1[] = {0x01, 0x02, 0x03, 0x04};
  const uint8_t d2[] = {0x05, 0x06, 0x07, 0x08};
  SECItem a = {siBuffer, const_cast<uint8_t *>(d1), sizeof(d1)};
  SECItem b = {siBuffer, const_cast<uint8_t *>(d2), sizeof(d2)};
  EXPECT_NE(SECITEM_Hash(&a), SECITEM_Hash(&b));
}

// Empty item: hash is 0 (XOR of nothing).
TEST_F(SecItemHashTest, HashEmpty) {
  SECItem item = {siBuffer, nullptr, 0};
  EXPECT_EQ(0U, SECITEM_Hash(&item));
}

// HashCompare delegates to ItemsAreEqual: returns non-zero for equal items.
TEST_F(SecItemHashTest, HashCompareEqualItems) {
  const uint8_t data[] = {0xAB, 0xCD};
  SECItem a = {siBuffer, const_cast<uint8_t *>(data), sizeof(data)};
  SECItem b = {siBuffer, const_cast<uint8_t *>(data), sizeof(data)};
  EXPECT_NE(0, SECITEM_HashCompare(&a, &b));
}

// HashCompare: returns 0 for unequal items.
TEST_F(SecItemHashTest, HashCompareUnequalItems) {
  const uint8_t d1[] = {0x01};
  const uint8_t d2[] = {0x02};
  SECItem a = {siBuffer, const_cast<uint8_t *>(d1), sizeof(d1)};
  SECItem b = {siBuffer, const_cast<uint8_t *>(d2), sizeof(d2)};
  EXPECT_EQ(0, SECITEM_HashCompare(&a, &b));
}

// ---------------------------------------------------------------------------
// SECITEM_AllocArray / SECITEM_DupArray / SECITEM_FreeArray /
// SECITEM_ZfreeArray
// ---------------------------------------------------------------------------

class SecItemArrayTest : public ::testing::Test {};

// AllocArray with NULL array: allocates the struct and a zero-filled items
// array.
TEST_F(SecItemArrayTest, AllocArrayHeapNullArray) {
  SECItemArray *arr = SECITEM_AllocArray(nullptr, nullptr, 3);
  ASSERT_TRUE(arr);
  EXPECT_EQ(3U, arr->len);
  ASSERT_TRUE(arr->items);
  for (unsigned int i = 0; i < arr->len; ++i) {
    EXPECT_FALSE(arr->items[i].data);
    EXPECT_EQ(0U, arr->items[i].len);
  }
  SECITEM_FreeArray(arr, PR_TRUE);
}

// AllocArray with existing array: allocates only the items buffer.
TEST_F(SecItemArrayTest, AllocArrayHeapExistingArray) {
  SECItemArray arr = {nullptr, 0};
  SECItemArray *result = SECITEM_AllocArray(nullptr, &arr, 2);
  ASSERT_EQ(&arr, result);
  EXPECT_EQ(2U, arr.len);
  ASSERT_TRUE(arr.items);
  SECITEM_FreeArray(&arr, PR_FALSE);
}

// len=0: items pointer is NULL.
TEST_F(SecItemArrayTest, AllocArrayZeroLen) {
  SECItemArray *arr = SECITEM_AllocArray(nullptr, nullptr, 0);
  ASSERT_TRUE(arr);
  EXPECT_EQ(0U, arr->len);
  EXPECT_FALSE(arr->items);
  SECITEM_FreeArray(arr, PR_TRUE);
}

TEST_F(SecItemArrayTest, AllocArrayArena) {
  ScopedPLArenaPool arena(PORT_NewArena(4096));
  ASSERT_TRUE(arena);
  SECItemArray *arr = SECITEM_AllocArray(arena.get(), nullptr, 4);
  ASSERT_TRUE(arr);
  EXPECT_EQ(4U, arr->len);
  EXPECT_TRUE(arr->items);
  // arr lives in the arena.
}

// DupArray: all items are deep-copied into freshly allocated buffers.
TEST_F(SecItemArrayTest, DupArrayBasic) {
  const uint8_t d0[] = {0x01, 0x02};
  const uint8_t d1[] = {0x03, 0x04, 0x05};
  SECItem items[2] = {
      {siBuffer, const_cast<uint8_t *>(d0), sizeof(d0)},
      {siBuffer, const_cast<uint8_t *>(d1), sizeof(d1)},
  };
  SECItemArray from = {items, 2};
  SECItemArray *dup = SECITEM_DupArray(nullptr, &from);
  ASSERT_TRUE(dup);
  ASSERT_EQ(2U, dup->len);
  EXPECT_EQ(sizeof(d0), dup->items[0].len);
  EXPECT_EQ(0, memcmp(dup->items[0].data, d0, sizeof(d0)));
  EXPECT_EQ(sizeof(d1), dup->items[1].len);
  EXPECT_EQ(0, memcmp(dup->items[1].data, d1, sizeof(d1)));
  EXPECT_NE(items[0].data, dup->items[0].data);
  SECITEM_FreeArray(dup, PR_TRUE);
}

// NULL from: must return NULL.
TEST_F(SecItemArrayTest, DupArrayNullFrom) {
  EXPECT_FALSE(SECITEM_DupArray(nullptr, nullptr));
}

// Zero-length from: valid — returns an empty array.
TEST_F(SecItemArrayTest, DupArrayEmptyFrom) {
  SECItemArray from = {nullptr, 0};
  SECItemArray *dup = SECITEM_DupArray(nullptr, &from);
  ASSERT_TRUE(dup);
  EXPECT_EQ(0U, dup->len);
  SECITEM_FreeArray(dup, PR_TRUE);
}

// DupArray with arena: struct and all item data come from the arena.
TEST_F(SecItemArrayTest, DupArrayArena) {
  ScopedPLArenaPool arena(PORT_NewArena(4096));
  ASSERT_TRUE(arena);
  const uint8_t d0[] = {0x01, 0x02};
  const uint8_t d1[] = {0x03};
  SECItem items[2] = {
      {siBuffer, const_cast<uint8_t *>(d0), sizeof(d0)},
      {siBuffer, const_cast<uint8_t *>(d1), sizeof(d1)},
  };
  SECItemArray from = {items, 2};
  SECItemArray *dup = SECITEM_DupArray(arena.get(), &from);
  ASSERT_TRUE(dup);
  ASSERT_EQ(2U, dup->len);
  EXPECT_EQ(0, memcmp(dup->items[0].data, d0, sizeof(d0)));
  EXPECT_EQ(0, memcmp(dup->items[1].data, d1, sizeof(d1)));
  // dup, dup->items, and all item data are arena-owned.
}

// Inconsistent from (NULL items but nonzero len): must return NULL.
TEST_F(SecItemArrayTest, DupArrayInconsistent) {
  SECItemArray from = {nullptr, 2};
  EXPECT_FALSE(SECITEM_DupArray(nullptr, &from));
}

// Mutating original after dup must not affect the dup.
TEST_F(SecItemArrayTest, DupArrayIndependence) {
  uint8_t data[] = {0xAA, 0xBB};
  SECItem items[1] = {{siBuffer, data, sizeof(data)}};
  SECItemArray from = {items, 1};
  SECItemArray *dup = SECITEM_DupArray(nullptr, &from);
  ASSERT_TRUE(dup);
  data[0] = 0xFF;
  EXPECT_EQ(0xAA, dup->items[0].data[0]);
  SECITEM_FreeArray(dup, PR_TRUE);
}

// freeit=PR_TRUE: frees the struct as well as all item data.
TEST_F(SecItemArrayTest, FreeArrayFreeit) {
  SECItemArray *arr = SECITEM_AllocArray(nullptr, nullptr, 2);
  ASSERT_TRUE(arr);
  SECITEM_FreeArray(arr, PR_TRUE);
}

// freeit=PR_FALSE: frees item data buffers; struct fields are cleared.
TEST_F(SecItemArrayTest, FreeArrayNoFreeit) {
  SECItemArray arr = {nullptr, 0};
  ASSERT_TRUE(SECITEM_AllocArray(nullptr, &arr, 2));
  SECITEM_FreeArray(&arr, PR_FALSE);
  EXPECT_FALSE(arr.items);
  EXPECT_EQ(0U, arr.len);
}

// NULL array: must not crash.
TEST_F(SecItemArrayTest, FreeArrayNull) {
  SECITEM_FreeArray(nullptr, PR_FALSE);
  SECITEM_FreeArray(nullptr, PR_TRUE);
}

// ZfreeArray: zeroes each item's data before freeing.
TEST_F(SecItemArrayTest, ZfreeArrayFreeit) {
  SECItemArray *arr = SECITEM_AllocArray(nullptr, nullptr, 2);
  ASSERT_TRUE(arr);
  arr->items[0].data = static_cast<uint8_t *>(PORT_Alloc(4));
  ASSERT_TRUE(arr->items[0].data);
  memset(arr->items[0].data, 0xCC, 4);
  arr->items[0].len = 4;
  SECITEM_ZfreeArray(arr, PR_TRUE);
}

// NULL array: must not crash.
TEST_F(SecItemArrayTest, ZfreeArrayNull) {
  SECITEM_ZfreeArray(nullptr, PR_FALSE);
  SECITEM_ZfreeArray(nullptr, PR_TRUE);
}

}  // namespace nss_test
