Add byte array math operations from NDecrypt; fix issues and add tests

This commit is contained in:
Matt Nadareski
2025-09-30 18:02:05 -04:00
parent f0fe9af467
commit 995c19d903
2 changed files with 250 additions and 0 deletions

View File

@@ -424,6 +424,86 @@ namespace SabreTools.IO.Test.Extensions
#endregion
#region Add
[Theory]
[InlineData(new byte[0], 0, new byte[0])]
[InlineData(new byte[0], 1234, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xD2 })]
[InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xD2 }, 0, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xD2 })]
[InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xD2 }, 1234, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0xA4 })]
public void Add_NumericInput(byte[] self, uint add, byte[] expected)
{
byte[] actual = self.Add(add);
Assert.Equal(expected.Length, actual.Length);
if (actual.Length > 0)
Assert.True(actual.EqualsExactly(expected));
}
[Theory]
[InlineData(new byte[0], new byte[0], new byte[0])]
[InlineData(new byte[0], new byte[] { 0x04, 0xD2 }, new byte[] { 0x04, 0xD2 })]
[InlineData(new byte[] { 0x04, 0xD2 }, new byte[0], new byte[] { 0x04, 0xD2 })]
[InlineData(new byte[] { 0x04, 0xD2 }, new byte[] { 0x00, 0x00 }, new byte[] { 0x04, 0xD2 })]
[InlineData(new byte[] { 0x00, 0x00 }, new byte[] { 0x04, 0xD2 }, new byte[] { 0x04, 0xD2 })]
[InlineData(new byte[] { 0x04, 0xD2 }, new byte[] { 0x04, 0xD2 }, new byte[] { 0x09, 0xA4 })]
[InlineData(new byte[] { 0xAB, 0x04, 0xD2 }, new byte[] { 0x04, 0xD2 }, new byte[] { 0xAB, 0x09, 0xA4 })]
[InlineData(new byte[] { 0x04, 0xD2 }, new byte[] { 0xAB, 0x04, 0xD2 }, new byte[] { 0xAB, 0x09, 0xA4 })]
public void Add_ArrayInput(byte[] self, byte[] add, byte[] expected)
{
byte[] actual = self.Add(add);
Assert.Equal(expected.Length, actual.Length);
if (actual.Length > 0)
Assert.True(actual.EqualsExactly(expected));
}
#endregion
#region RotateLeft
[Theory]
[InlineData(new byte[0], 0, new byte[0])]
[InlineData(new byte[] { 0x01 }, 0, new byte[] { 0x01 })]
[InlineData(new byte[] { 0x01 }, 1, new byte[] { 0x02 })]
[InlineData(new byte[] { 0x80 }, 1, new byte[] { 0x01 })]
[InlineData(new byte[] { 0x00, 0x01 }, 0, new byte[] { 0x00, 0x01 })]
[InlineData(new byte[] { 0x00, 0x01 }, 1, new byte[] { 0x00, 0x02 })]
[InlineData(new byte[] { 0x00, 0x80 }, 1, new byte[] { 0x01, 0x00 })]
[InlineData(new byte[] { 0x80, 0x00 }, 1, new byte[] { 0x00, 0x01 })]
public void RotateLeftTest(byte[] self, int numBits, byte[] expected)
{
byte[] actual = self.RotateLeft(numBits);
Assert.Equal(expected.Length, actual.Length);
if (actual.Length > 0)
Assert.True(actual.EqualsExactly(expected));
}
#endregion
#region Xor
[Theory]
[InlineData(new byte[0], new byte[0], new byte[0])]
[InlineData(new byte[0], new byte[] { 0x04, 0xD2 }, new byte[] { 0x04, 0xD2 })]
[InlineData(new byte[] { 0x04, 0xD2 }, new byte[0], new byte[] { 0x04, 0xD2 })]
[InlineData(new byte[] { 0x04, 0xD2 }, new byte[] { 0x00, 0x00 }, new byte[] { 0x04, 0xD2 })]
[InlineData(new byte[] { 0x00, 0x00 }, new byte[] { 0x04, 0xD2 }, new byte[] { 0x04, 0xD2 })]
[InlineData(new byte[] { 0x04, 0xD2 }, new byte[] { 0x04, 0xD2 }, new byte[] { 0x00, 0x00 })]
[InlineData(new byte[] { 0xAB, 0x04, 0xD2 }, new byte[] { 0x04, 0xD2 }, new byte[] { 0xAB, 0x00, 0x00 })]
[InlineData(new byte[] { 0x04, 0xD2 }, new byte[] { 0xAB, 0x04, 0xD2 }, new byte[] { 0xAB, 0x00, 0x00 })]
public void XorTest(byte[] self, byte[] add, byte[] expected)
{
byte[] actual = self.Xor(add);
Assert.Equal(expected.Length, actual.Length);
if (actual.Length > 0)
Assert.True(actual.EqualsExactly(expected));
}
#endregion
#region ToHexString
[Fact]

View File

@@ -237,6 +237,176 @@ namespace SabreTools.IO.Extensions
#endregion
#region Math
/// <summary>
/// Add an integer value to a number represented by a byte array
/// </summary>
/// <param name="self">Byte array to add to</param>
/// <param name="add">Amount to add</param>
/// <returns>Byte array representing the new value</returns>
/// <remarks>Assumes array values are in big-endian format</remarks>
public static byte[] Add(this byte[] self, uint add)
{
// If nothing is being added, just return
if (add == 0)
return self;
// Get the big-endian representation of the value
byte[] addBytes = BitConverter.GetBytes(add);
Array.Reverse(addBytes);
// Pad the array out to 16 bytes
byte[] paddedBytes = new byte[16];
Array.Copy(addBytes, 0, paddedBytes, 12, 4);
// If the input is empty, just return the added value
if (self.Length == 0)
return paddedBytes;
return self.Add(paddedBytes);
}
/// <summary>
/// Add two numbers represented by byte arrays
/// </summary>
/// <param name="self">Byte array to add to</param>
/// <param name="add">Amount to add</param>
/// <returns>Byte array representing the new value</returns>
/// <remarks>Assumes array values are in big-endian format</remarks>
public static byte[] Add(this byte[] self, byte[] add)
{
// If either input is empty
if (self.Length == 0 && add.Length == 0)
return [];
else if (self.Length > 0 && add.Length == 0)
return self;
else if (self.Length == 0 && add.Length > 0)
return add;
// Setup the output array
int outLength = Math.Max(self.Length, add.Length);
byte[] output = new byte[outLength];
// Loop adding with carry
uint carry = 0;
for (int i = 0; i < outLength; i++)
{
int selfIndex = self.Length - i - 1;
uint selfValue = selfIndex >= 0 ? self[selfIndex] : 0u;
int addIndex = add.Length - i - 1;
uint addValue = addIndex >= 0 ? add[addIndex] : 0u;
uint next = selfValue + addValue + carry;
carry = next >> 8;
int outputIndex = output.Length - i - 1;
output[outputIndex] = (byte)(next & 0xFF);
}
return output;
}
/// <summary>
/// Perform a rotate left on a byte array
/// </summary>
/// <param name="self">Byte array value to rotate</param>
/// <param name="numBits">Number of bits to rotate</param>
/// <returns>Rotated byte array value</returns>
/// <remarks>Assumes array values are in big-endian format</remarks>
public static byte[] RotateLeft(this byte[] self, int numBits)
{
// If either input is empty
if (self.Length == 0)
return [];
else if (numBits == 0)
return self;
byte[] output = new byte[self.Length];
Array.Copy(self, output, output.Length);
// Shift by bytes
while (numBits >= 8)
{
byte temp = output[0];
for (int i = 0; i < output.Length - 1; i++)
{
output[i] = output[i + 1];
}
output[output.Length - 1] = temp;
numBits -= 8;
}
// Shift by bits
if (numBits > 0)
{
byte bitMask = (byte)(8 - numBits), carry, wrap = 0;
for (int i = 0; i < output.Length; i++)
{
carry = (byte)((255 << bitMask & output[i]) >> bitMask);
// Make sure the first byte carries to the end
if (i == 0)
wrap = carry;
// Otherwise, move to the last byte
else
output[i - 1] |= carry;
// Shift the current bits
output[i] <<= numBits;
}
// Make sure the wrap happens
output[output.Length - 1] |= wrap;
}
return output;
}
/// <summary>
/// XOR two numbers represented by byte arrays
/// </summary>
/// <param name="self">Byte array to XOR to</param>
/// <param name="xor">Amount to XOR</param>
/// <returns>Byte array representing the new value</returns>
/// <remarks>Assumes array values are in big-endian format</remarks>
public static byte[] Xor(this byte[] self, byte[] xor)
{
// If either input is empty
if (self.Length == 0 && xor.Length == 0)
return [];
else if (self.Length > 0 && xor.Length == 0)
return self;
else if (self.Length == 0 && xor.Length > 0)
return xor;
// Setup the output array
int outLength = Math.Max(self.Length, xor.Length);
byte[] output = new byte[outLength];
// Loop XOR
for (int i = 0; i < outLength; i++)
{
int selfIndex = self.Length - i - 1;
uint selfValue = selfIndex >= 0 ? self[selfIndex] : 0u;
int xorIndex = xor.Length - i - 1;
uint xorValue = xorIndex >= 0 ? xor[xorIndex] : 0u;
uint next = selfValue ^ xorValue;
int outputIndex = output.Length - i - 1;
output[outputIndex] = (byte)(next & 0xFF);
}
return output;
}
#endregion
#region Strings
/// <summary>