252 lines
8.7 KiB
C#
252 lines
8.7 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Security.Cryptography;
|
|
using System.Text.Json;
|
|
using Paca.Core.Net;
|
|
using Paca.Core.Security;
|
|
using Xunit;
|
|
|
|
namespace Paca.Tests.Core;
|
|
|
|
public class P2pAndE2eeVaultExtremeRedTests
|
|
{
|
|
// ==========================================
|
|
// 1. P2pSwarmEngine Extreme Scenarios
|
|
// ==========================================
|
|
|
|
[Theory]
|
|
[InlineData(0)]
|
|
[InlineData(-1024)]
|
|
public void P2pSwarmEngine_CreateManifestFromBytes_ZeroOrNegativePieceLength_ClampsToDefault512KB(int pieceLength)
|
|
{
|
|
byte[] dummyData = new byte[1024 * 1024]; // 1MB
|
|
var manifest = P2pSwarmEngine.CreateManifestFromBytes("test.mp4", dummyData, pieceLength);
|
|
Assert.NotNull(manifest);
|
|
Assert.True(manifest.PieceLength >= 16 * 1024, "PieceLength must be safely clamped to at least 16KB");
|
|
}
|
|
|
|
[Fact]
|
|
public void P2pSwarmEngine_CreateManifestFromBytes_NullData_ThrowsArgumentNullException()
|
|
{
|
|
Assert.Throws<ArgumentNullException>(() =>
|
|
P2pSwarmEngine.CreateManifestFromBytes("test.mp4", null!));
|
|
}
|
|
|
|
[Fact]
|
|
public void P2pSwarmEngine_InitializeSession_NullManifest_ThrowsArgumentNullException()
|
|
{
|
|
var engine = new P2pSwarmEngine();
|
|
Assert.Throws<ArgumentNullException>(() => engine.InitializeSession(null!));
|
|
}
|
|
|
|
[Fact]
|
|
public void P2pSwarmEngine_AddPeer_NullPeer_DoesNotThrow()
|
|
{
|
|
var engine = new P2pSwarmEngine();
|
|
var ex = Record.Exception(() => engine.AddPeer(null!));
|
|
Assert.Null(ex);
|
|
Assert.Equal(0, engine.ConnectedPeerCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void P2pSwarmEngine_AddPeer_NullBitfield_InitializesSafeBitfield()
|
|
{
|
|
var engine = new P2pSwarmEngine();
|
|
var peer = new SwarmPeer { PeerId = "peer1", Bitfield = null! };
|
|
var ex = Record.Exception(() => engine.AddPeer(peer));
|
|
Assert.Null(ex);
|
|
Assert.NotNull(peer.Bitfield);
|
|
}
|
|
|
|
[Fact]
|
|
public void P2pSwarmEngine_IngestPieceData_NullData_ReturnsFalseWithoutThrowing()
|
|
{
|
|
var engine = new P2pSwarmEngine();
|
|
var manifest = P2pSwarmEngine.CreateManifestFromBytes("sample.mp4", new byte[100], 50);
|
|
engine.InitializeSession(manifest);
|
|
|
|
bool ingested = engine.IngestPieceData(0, null!);
|
|
Assert.False(ingested);
|
|
}
|
|
|
|
[Fact]
|
|
public void P2pSwarmEngine_IngestPieceData_LengthMismatch_ReturnsFalse()
|
|
{
|
|
var engine = new P2pSwarmEngine();
|
|
var data = new byte[100];
|
|
var manifest = P2pSwarmEngine.CreateManifestFromBytes("sample.mp4", data, 50);
|
|
engine.InitializeSession(manifest);
|
|
|
|
// Piece 0 expects 50 bytes, passing 10 bytes must fail even if empty
|
|
bool ingested = engine.IngestPieceData(0, new byte[10]);
|
|
Assert.False(ingested);
|
|
}
|
|
|
|
// ==========================================
|
|
// 2. CredentialDataExchange Extreme Scenarios
|
|
// ==========================================
|
|
|
|
[Fact]
|
|
public void CredentialDataExchange_ExportEncryptedVault_NullRecords_ThrowsArgumentNullException()
|
|
{
|
|
Assert.Throws<ArgumentNullException>(() =>
|
|
CredentialDataExchange.ExportEncryptedVault(null!, "MasterPass123!"));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(null)]
|
|
[InlineData("")]
|
|
[InlineData(" ")]
|
|
public void CredentialDataExchange_ExportEncryptedVault_NullOrWhitespacePassword_ThrowsArgumentException(string? badPass)
|
|
{
|
|
var records = new List<SiteCredentialRecord>
|
|
{
|
|
new() { Id = "1", Domain = "example.com", Username = "user", DecryptedPassword = "pw" }
|
|
};
|
|
Assert.Throws<ArgumentException>(() =>
|
|
CredentialDataExchange.ExportEncryptedVault(records, badPass!));
|
|
}
|
|
|
|
[Fact]
|
|
public void CredentialDataExchange_ImportEncryptedVault_DeclaredLengthExceedsAvailableData_ThrowsInvalidDataException()
|
|
{
|
|
// Valid header with huge declared ciphertext length
|
|
byte[] magic = System.Text.Encoding.ASCII.GetBytes("PACAVAULT_V1");
|
|
using var ms = new MemoryStream();
|
|
using var bw = new BinaryWriter(ms);
|
|
bw.Write(magic);
|
|
bw.Write(new byte[32]); // salt
|
|
bw.Write(new byte[12]); // nonce
|
|
bw.Write(new byte[16]); // tag
|
|
bw.Write(10_000_000); // claimed 10MB
|
|
bw.Write(new byte[10]); // but only 10 bytes present!
|
|
bw.Flush();
|
|
|
|
var malformedData = ms.ToArray();
|
|
Assert.Throws<InvalidDataException>(() =>
|
|
CredentialDataExchange.ImportEncryptedVault(malformedData, "MasterPass123!"));
|
|
}
|
|
|
|
[Fact]
|
|
public void CredentialDataExchange_ExportCsv_NullRecords_ReturnsHeaderOnly()
|
|
{
|
|
var csv = CredentialDataExchange.ExportCsv(null!);
|
|
Assert.NotNull(csv);
|
|
Assert.Contains("name,url,username,password", csv);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(null)]
|
|
[InlineData("")]
|
|
[InlineData(" ")]
|
|
public void CredentialDataExchange_ImportCsv_NullOrWhitespace_ReturnsEmptyList(string? badCsv)
|
|
{
|
|
var list = CredentialDataExchange.ImportCsv(badCsv!);
|
|
Assert.NotNull(list);
|
|
Assert.Empty(list);
|
|
}
|
|
|
|
[Fact]
|
|
public void CredentialDataExchange_ExportCsv_FormulaInjectionPrefixes_SanitizedSafely()
|
|
{
|
|
var maliciousRecord = new SiteCredentialRecord
|
|
{
|
|
Id = "evil",
|
|
SiteName = "=cmd|' /C calc'!A0",
|
|
Domain = "+1234567890",
|
|
Username = "@dangerousUser",
|
|
DecryptedPassword = "-unsafePassword",
|
|
Notes = "\tmaliciousTab"
|
|
};
|
|
|
|
var csv = CredentialDataExchange.ExportCsv(new[] { maliciousRecord });
|
|
Assert.NotNull(csv);
|
|
// Formula injection prefixes (=, +, -, @, tab) should be sanitized
|
|
var lines = csv.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
|
Assert.True(lines.Length >= 2);
|
|
string dataRow = lines[1];
|
|
Assert.DoesNotContain(",=cmd", dataRow);
|
|
Assert.DoesNotContain(",+123", dataRow);
|
|
Assert.DoesNotContain(",@dan", dataRow);
|
|
}
|
|
|
|
// ==========================================
|
|
// 3. CrdtVaultSyncEngine Extreme Scenarios
|
|
// ==========================================
|
|
|
|
[Fact]
|
|
public void CrdtVaultSyncEngine_Merge_NullRemote_ReturnsZeroWithoutThrowing()
|
|
{
|
|
var engine = new CrdtVaultSyncEngine<string>();
|
|
int applied = engine.Merge(null!);
|
|
Assert.Equal(0, applied);
|
|
}
|
|
|
|
[Fact]
|
|
public void CrdtVaultSyncEngine_Merge_NullOrEmptyIds_SkippedSafely()
|
|
{
|
|
var engine = new CrdtVaultSyncEngine<string>();
|
|
var items = new List<CrdtItemRecord<string>>
|
|
{
|
|
null!,
|
|
new() { Id = null!, Value = "A" },
|
|
new() { Id = "", Value = "B" },
|
|
new() { Id = "valid_1", Value = "C", LamportTimestamp = 1 }
|
|
};
|
|
|
|
int applied = engine.Merge(items);
|
|
Assert.Equal(1, applied);
|
|
Assert.Single(engine.GetActiveItems());
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(null)]
|
|
[InlineData("")]
|
|
[InlineData(" ")]
|
|
public void CrdtVaultSyncEngine_Set_NullOrEmptyId_ThrowsArgumentException(string? invalidId)
|
|
{
|
|
var engine = new CrdtVaultSyncEngine<string>();
|
|
Assert.Throws<ArgumentException>(() => engine.Set(invalidId!, "value"));
|
|
}
|
|
|
|
// ==========================================
|
|
// 4. E2eeRemoteBackupProvider Extreme Scenarios
|
|
// ==========================================
|
|
|
|
[Theory]
|
|
[InlineData(null)]
|
|
[InlineData("")]
|
|
[InlineData(" ")]
|
|
public void E2eeRemoteBackupProvider_EncryptPayload_NullOrEmptyPassword_ThrowsArgumentException(string? badPass)
|
|
{
|
|
Assert.Throws<ArgumentException>(() =>
|
|
E2eeRemoteBackupProvider.EncryptPayload("{}", badPass!));
|
|
}
|
|
|
|
[Fact]
|
|
public void E2eeRemoteBackupProvider_DecryptPayload_InvalidMagic_ThrowsInvalidDataException()
|
|
{
|
|
var bogusHeader = new VaultBackupHeader
|
|
{
|
|
Magic = "HACKERVAULT",
|
|
SaltBase64 = Convert.ToBase64String(new byte[16]),
|
|
NonceBase64 = Convert.ToBase64String(new byte[12]),
|
|
TagBase64 = Convert.ToBase64String(new byte[16]),
|
|
CiphertextBase64 = Convert.ToBase64String(new byte[32])
|
|
};
|
|
string json = JsonSerializer.Serialize(bogusHeader);
|
|
|
|
Assert.Throws<InvalidDataException>(() =>
|
|
E2eeRemoteBackupProvider.DecryptPayload(json, "MasterPassword123!"));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("not-json")]
|
|
[InlineData("{\"Magic\":\"PACAVAULT\",\"SaltBase64\":\"%%%not-base64%%%\"}")]
|
|
public void E2eeRemoteBackupProvider_DecryptPayload_CorruptedBase64_ThrowsInvalidDataException(string corruptJson)
|
|
{
|
|
Assert.Throws<InvalidDataException>(() =>
|
|
E2eeRemoteBackupProvider.DecryptPayload(corruptJson, "MasterPassword123!"));
|
|
}
|
|
}
|