using SabreTools.Data.Models.UDF;
using Xunit;
namespace SabreTools.Data.Extensions.Test
{
public class UDFExtensionsTests
{
#region DecodeDCharacters
[Fact]
public void DecodeDCharacters_Null_Empty()
{
byte[]? data = null;
Assert.Equal(string.Empty, data.DecodeDCharacters());
}
[Fact]
public void DecodeDCharacters_8Bit_Decoded()
{
byte[] data = [8, 0x41, 0x62, 0xE4];
Assert.Equal("Abä", data.DecodeDCharacters());
}
[Fact]
public void DecodeDCharacters_16Bit_Decoded()
{
byte[] data = [16, 0x00, 0x41, 0x30, 0x42];
Assert.Equal("Aあ", data.DecodeDCharacters());
}
[Fact]
public void DecodeDCharacters_UnknownCompression_Null()
{
byte[] data = [1, 0x41];
Assert.Null(data.DecodeDCharacters());
}
[Fact]
public void DecodeDString_UsesLengthByte()
{
byte[] data = [8, 0x41, 0x42, 0x43, 0x00, 3];
Assert.Equal("AB", data.DecodeDString());
}
[Fact]
public void DecodeDString_ZeroLength_Empty()
{
byte[] data = [8, 0x41, 0x00, 0];
Assert.Equal(string.Empty, data.DecodeDString());
}
#endregion
#region TryGetSector
[Fact]
public void TryGetSector_Type1_StartPlusBlock()
{
var volume = CreateVolume(new Type1PartitionMap { PartitionNumber = 0 });
bool actual = volume.TryGetSector(UDFExtensions.GetKey(0, 10), out long sector);
Assert.True(actual);
Assert.Equal(1010, sector);
}
[Fact]
public void TryGetSector_Type1_OutsidePartition_False()
{
var volume = CreateVolume(new Type1PartitionMap { PartitionNumber = 0 });
bool actual = volume.TryGetSector(UDFExtensions.GetKey(0, 100), out _);
Assert.False(actual);
}
[Fact]
public void TryGetSector_UnknownPartitionReference_False()
{
var volume = CreateVolume(new Type1PartitionMap { PartitionNumber = 0 });
bool actual = volume.TryGetSector(UDFExtensions.GetKey(1, 0), out _);
Assert.False(actual);
}
[Fact]
public void TryGetSector_Metadata_ThroughMetadataFile()
{
// Metadata File at block 5 of partition 0: blocks 0-1 of the metadata partition are blocks 20-21,
// blocks 2-3 are blocks 40-41
var volume = CreateVolume(
new Type1PartitionMap { PartitionNumber = 0 },
new MetadataPartitionMap { PartitionNumber = 0, MetadataFileLocation = 5, MetadataMirrorFileLocation = 6 });
volume.FileEntries[UDFExtensions.GetKey(0, 5)] = CreateShortAdEntry((4096, 20), (4096, 40));
Assert.True(volume.TryGetSector(UDFExtensions.GetKey(1, 1), out long first));
Assert.Equal(1021, first);
Assert.True(volume.TryGetSector(UDFExtensions.GetKey(1, 2), out long second));
Assert.Equal(1040, second);
Assert.False(volume.TryGetSector(UDFExtensions.GetKey(1, 4), out _));
}
[Fact]
public void TryGetSector_Metadata_FallsBackToMirror()
{
var volume = CreateVolume(
new Type1PartitionMap { PartitionNumber = 0 },
new MetadataPartitionMap { PartitionNumber = 0, MetadataFileLocation = 5, MetadataMirrorFileLocation = 6 });
volume.FileEntries[UDFExtensions.GetKey(0, 6)] = CreateShortAdEntry((2048, 30));
Assert.True(volume.TryGetSector(UDFExtensions.GetKey(1, 0), out long sector));
Assert.Equal(1030, sector);
}
#endregion
#region GetAllocationDescriptors
[Fact]
public void GetAllocationDescriptors_Embedded_Empty()
{
var entry = new FileEntry();
entry.ICBTag.Flags = (ushort)AllocationDescriptorType.EMBEDDED;
entry.AllocationDescriptors = [1, 2, 3];
var actual = new Volume().GetAllocationDescriptors(0, entry);
Assert.NotNull(actual);
Assert.Empty(actual);
}
[Fact]
public void GetAllocationDescriptors_ZeroLengthTerminates()
{
var entry = CreateShortAdEntry((2048, 20), (0, 99), (2048, 40));
var actual = new Volume().GetAllocationDescriptors(UDFExtensions.GetKey(2, 7), entry);
Assert.NotNull(actual);
var ad = Assert.Single(actual);
Assert.Equal(20u, ad.ExtentLocation.LogicalBlockNumber);
Assert.Equal((ushort)2, ad.ExtentLocation.PartitionReferenceNumber);
}
[Fact]
public void GetAllocationDescriptors_MissingAllocationExtent_NullWithKey()
{
var entry = CreateShortAdEntry((2048, 20), ((3u << 30) | 2048, 50));
var actual = new Volume().GetAllocationDescriptors(UDFExtensions.GetKey(0, 7), entry, out long missingKey);
Assert.Null(actual);
Assert.Equal(UDFExtensions.GetKey(0, 50), missingKey);
}
[Fact]
public void GetAllocationDescriptors_AllocationExtentLoop_Null()
{
var volume = new Volume();
var entry = CreateShortAdEntry(((3u << 30) | 2048, 50));
volume.AllocationExtentDescriptors[UDFExtensions.GetKey(0, 50)] = new AllocationExtentDescriptor
{
AllocationDescriptors = CreateShortAdEntry(((3u << 30) | 2048, 50)).AllocationDescriptors,
};
var actual = volume.GetAllocationDescriptors(UDFExtensions.GetKey(0, 7), entry);
Assert.Null(actual);
}
#endregion
#region IsValidDescriptor
[Theory]
[InlineData(2)]
[InlineData(3)]
public void IsValidDescriptor_Valid_True(byte version)
{
byte[] data = CreateDescriptor(version);
Assert.True(data.IsValidDescriptor(0, data.Length, 20));
}
[Fact]
public void IsValidDescriptor_WrongChecksum_False()
{
byte[] data = CreateDescriptor(2);
data[4]++;
Assert.False(data.IsValidDescriptor(0, data.Length, 20));
}
[Fact]
public void IsValidDescriptor_UnknownVersion_False()
{
byte[] data = CreateDescriptor(4);
Assert.False(data.IsValidDescriptor(0, data.Length, 20));
}
[Fact]
public void IsValidDescriptor_WrongCrc_False()
{
byte[] data = CreateDescriptor(2);
data[16] ^= 0x01;
Assert.False(data.IsValidDescriptor(0, data.Length, 20));
}
[Fact]
public void IsValidDescriptor_CrcPastAvailable_NotChecked()
{
byte[] data = CreateDescriptor(2);
data[16] ^= 0x01;
Assert.True(data.IsValidDescriptor(0, 16, 20));
}
[Fact]
public void IsValidDescriptor_WrongLocation_False()
{
byte[] data = CreateDescriptor(2);
Assert.False(data.IsValidDescriptor(0, data.Length, 21));
}
#endregion
///
/// Descriptor with tag location 20 whose 3 bytes after the tag have the CRC #3299 (ECMA-167 3/7.2.6)
///
private static byte[] CreateDescriptor(byte version)
{
byte[] data = new byte[19];
data[0] = 1;
data[2] = version;
data[8] = 0x99;
data[9] = 0x32;
data[10] = 3;
data[12] = 20;
data[16] = 0x70;
data[17] = 0x6A;
data[18] = 0x77;
for (int i = 0; i < 16; i++)
{
if (i != 4)
data[4] += data[i];
}
return data;
}
///
/// Volume with partition 0 at sector 1000 (length 50) and the given partition maps
///
private static Volume CreateVolume(params PartitionMap[] maps)
{
return new Volume
{
VolumeDescriptorSequence =
[
new PartitionDescriptor { PartitionNumber = 0, PartitionStartingLocation = 1000, PartitionLength = 50 },
new LogicalVolumeDescriptor { LogicalBlockSize = 2048, PartitionMaps = maps },
],
};
}
///
/// File Entry with the given short allocation descriptors (extent length, position)
///
private static FileEntry CreateShortAdEntry(params (uint Length, uint Position)[] extents)
{
var data = new byte[extents.Length * 8];
for (int i = 0; i < extents.Length; i++)
{
for (int b = 0; b < 4; b++)
{
data[(i * 8) + b] = (byte)(extents[i].Length >> (8 * b));
data[(i * 8) + 4 + b] = (byte)(extents[i].Position >> (8 * b));
}
}
var entry = new FileEntry { AllocationDescriptors = data };
entry.ICBTag.Flags = (ushort)AllocationDescriptorType.SHORT;
return entry;
}
}
}