Skip to content

Commit ae699cb

Browse files
J0EK3RJ0EK3R
andauthored
Add Unit Tests for CryptTools (#144)
* JK: implemented method "Decrypt" for CaDA control datagrams * JK: unit tests for CryptTools.GetRfPayload * JK: GetRfPayload takes header array as parameter * JK: fixed variable name * JK: added some more unit-tests * JK: added more testdata * JK: splitted unittest into two scenarios Encrypt/Decrypt * JK: Reverse lookup table for SwitchSheet transformation * JK: renemed Invert8 and Invert16 to Reverse * optimized reverting --------- Co-authored-by: J0EK3R <J0EK3R3.3@WIN11>
1 parent 116b4c1 commit ae699cb

6 files changed

Lines changed: 244 additions & 39 deletions

File tree

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using FluentAssertions;
2+
using System.Linq;
3+
using Xunit;
4+
using BrickController2.Protocols;
5+
using BrickController2.Tools.Protocols;
6+
7+
namespace BrickController2.Tests.Protocols;
8+
9+
public class CaDAProtocolTests
10+
{
11+
[Theory]
12+
[InlineData(new byte[] { 0xaa, 0xbb, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde },
13+
new byte[] { 0x8a, 0xfb, 0x50, 0xfa, 0xdd, 0x73, 0x44, 0xcb, 0xde })]
14+
public void CaDAProtocol_Encrypt_ShouldReturnExpectedByteArray(byte[] input, byte[] expected)
15+
{
16+
byte[] copy = input.ToArray(); // Ensure we work with a copy
17+
CaDAProtocol.Encrypt(copy);
18+
19+
copy.Should().BeEquivalentTo(expected);
20+
}
21+
22+
[Theory]
23+
[InlineData(new byte[] { 0x8a, 0xfb, 0x50, 0xfa, 0xdd, 0x73, 0x44, 0xcb, 0xde },
24+
new byte[] { 0xaa, 0xbb, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde })]
25+
public void CaDAProtocol_Decrypt_ShouldReturnExpectedByteArray(byte[] input, byte[] expected)
26+
{
27+
byte[] copy = input.ToArray(); // Ensure we work with a copy
28+
CaDATools.Decrypt(copy);
29+
30+
copy.Should().BeEquivalentTo(expected);
31+
}
32+
}

BrickController2/BrickController2.Tests/Protocols/CryptToolsTests.cs

Lines changed: 81 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using Xunit;
22
using FluentAssertions;
33
using BrickController2.Protocols;
4-
using BrickController2.Tools.Protocols;
54

65
namespace BrickController2.Tests.Protocols;
76

@@ -28,9 +27,6 @@ public void GetRfPayload_CaDA_ShouldReturnExpectedByteArray(int headerOffset, by
2827

2928
size.Should().Be(expectedSize);
3029
rfPayload.Should().BeEquivalentTo(expected);
31-
32-
byte[] decrypt = DecryptTools.DecryptRfPayload(CaDAProtocol.SeedArray, CaDAProtocol.HeaderArray.Length, input.Length, headerOffset, CaDAProtocol.CTXValue1, CaDAProtocol.CTXValue2, rfPayload);
33-
decrypt.Should().BeEquivalentTo(input);
3430
}
3531

3632
[Theory]
@@ -59,8 +55,87 @@ public void GetRfPayload_MouldKing_ShouldReturnExpectedByteArray(int headerOffse
5955

6056
size.Should().Be(expectedSize);
6157
rfPayload.Should().BeEquivalentTo(expected);
58+
}
59+
60+
[Theory]
61+
[InlineData(0b00000000, 0x00000000)]
62+
[InlineData(0b11111111, 0b11111111)]
63+
[InlineData(0b10101010, 0b01010101)]
64+
[InlineData(0b11110000, 0b00001111)]
65+
public void Reverse_byte_ShouldReturnExpectedValue(byte input, byte expected)
66+
{
67+
byte result = CryptTools.Reverse(input);
68+
69+
result.Should().Be(expected);
70+
}
71+
72+
[Theory]
73+
[InlineData(0b0000000000000000, 0x0000000000000000)]
74+
[InlineData(0b1111111111111111, 0b1111111111111111)]
75+
[InlineData(0b1010101010101010, 0b0101010101010101)]
76+
[InlineData(0b1111000011110000, 0b0000111100001111)]
77+
public void Reverse_ushort_ShouldReturnExpectedValue(ushort input, ushort expected)
78+
{
79+
ushort result = CryptTools.Reverse(input);
80+
81+
result.Should().Be(expected);
82+
}
83+
84+
[Theory]
85+
[InlineData(
86+
new byte[] { 0xc1, 0xc2, 0xc3, 0xc4, 0xc5 },
87+
new byte[] { 0xAD, 0x7B, 0xA7, 0x80, 0x80, 0x80, 0x4F, 0x52 },
88+
0xabd3)]
89+
[InlineData(
90+
new byte[] { 0x43, 0x41, 0x52 },
91+
new byte[] { 0x75, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00 },
92+
0x0b5e)]
93+
public void CheckCRC16_ShouldReturnExpectedValue(byte[] input1, byte[] input2, ushort expected)
94+
{
95+
ushort result = CryptTools.CheckCRC16(input1, input2);
96+
97+
result.Should().Be(expected);
98+
}
99+
100+
[Theory]
101+
[InlineData(MKProtocol.CTXValue1, new byte[] { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 })]
102+
[InlineData(MKProtocol.CTXValue2, new byte[] { 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01 })]
103+
[InlineData(CaDAProtocol.CTXValue2, new byte[] { 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00 })]
104+
public void WhiteningInit_ShouldReturnExpectedArray(byte input, byte[] expected)
105+
{
106+
byte[] result = new byte[7];
107+
CryptTools.WhiteningInit(input, result);
108+
109+
result.Should().BeEquivalentTo(expected);
110+
}
111+
112+
[Theory]
113+
[InlineData(
114+
new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8e, 0xf0, 0xaa, 0xa3, 0x23, 0xc3, 0x43, 0x83, 0xad, 0x7b, 0xa7, 0x80, 0x80, 0x80, 0x4f, 0x52, 0xd3, 0xab },
115+
0x12, 0x0f,
116+
MKProtocol.CTXValue1,
117+
new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8e, 0xf0, 0xaa, 0x64, 0xae, 0x11, 0x14, 0x22, 0x90, 0xdc, 0xc1, 0x30, 0xf5, 0xb1, 0x5e, 0x1a, 0x45, 0xdc })]
118+
[InlineData(
119+
new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8e, 0xf0, 0xaa, 0x64, 0xae, 0x11, 0x14, 0x22, 0x90, 0xdc, 0xc1, 0x30, 0xf5, 0xb1, 0x5e, 0x1a, 0x45, 0xdc },
120+
0x00, 0x21,
121+
MKProtocol.CTXValue2,
122+
new byte[] { 0x8d, 0xd2, 0x57, 0xa1, 0x3d, 0xa7, 0x66, 0xb0, 0x75, 0x31, 0x11, 0x48, 0x96, 0x77, 0xf8, 0x6d, 0xb6, 0x43, 0xcf, 0x7e, 0x8f, 0x47, 0x11, 0x48, 0x66, 0x59, 0x38, 0xd1, 0x7a, 0x65, 0xe6, 0x34, 0x7f })]
123+
[InlineData(
124+
new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8e, 0xf0, 0xaa, 0x4a, 0x82, 0xc2, 0x75, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x5e, 0x0b },
125+
0x10, 0x15,
126+
CaDAProtocol.CTXValue1,
127+
new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8e, 0xf0, 0xaa, 0x8d, 0x0f, 0x10, 0x22, 0xb2, 0x3d, 0xa7, 0x66, 0xb0, 0x75, 0x31, 0x11, 0x48, 0x16, 0xf7, 0xf8, 0xe3, 0x46, 0xe9, 0xf5, 0xdb })]
128+
[InlineData(
129+
new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8e, 0xf0, 0xaa, 0x8d, 0x0f, 0x10, 0x22, 0xb2, 0x3d, 0xa7, 0x66, 0xb0, 0x75, 0x31, 0x11, 0x48, 0x16, 0xf7, 0xf8, 0xe3, 0x46, 0xe9, 0xf5, 0xdb },
130+
0x00, 0x25,
131+
CaDAProtocol.CTXValue2,
132+
new byte[] { 0xd6, 0xc5, 0x44, 0x20, 0x59, 0xde, 0xe1, 0x8f, 0x1b, 0xa5, 0xaf, 0x42, 0x7b, 0xc0, 0x3d, 0xca, 0x66, 0x6d, 0x32, 0xb2, 0x9e, 0xd2, 0x57, 0xa1, 0x3d, 0xa7, 0x66, 0xb0, 0x75, 0xb1, 0x91, 0x48, 0x96, 0x77, 0xf8, 0xbd, 0x4d })]
133+
public void WhiteningEncode_ShouldReturnExpectedArray(byte[] input, int seedOffset, int len, byte ctx, byte[] expected)
134+
{
135+
byte[] ctxArray = new byte[7];
136+
CryptTools.WhiteningInit(ctx, ctxArray);
137+
CryptTools.WhiteningEncode(input, seedOffset, len, ctxArray);
62138

63-
byte[] decrypt = DecryptTools.DecryptRfPayload(MKProtocol.SeedArray, MKProtocol.HeaderArray.Length, input.Length, headerOffset, MKProtocol.CTXValue1, MKProtocol.CTXValue2, rfPayload);
64-
decrypt.Should().BeEquivalentTo(input);
139+
input.Should().BeEquivalentTo(expected);
65140
}
66141
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
using BrickController2.Protocols;
2+
3+
namespace BrickController2.Tools.Protocols;
4+
5+
public static class CaDATools
6+
{
7+
// Reverse lookup table for SwitchSheet transformation
8+
private static readonly byte[] ReverseSwitchSheet = CreateReverseSwitchSheet();
9+
10+
private static byte[] CreateReverseSwitchSheet()
11+
{
12+
var table = new byte[256];
13+
for (int orig = 0; orig < 256; orig++)
14+
{
15+
byte candidate = (byte)(CaDAProtocol.SwitchSheet[orig / 4] + orig % 4);
16+
table[candidate] = (byte)orig;
17+
}
18+
return table;
19+
}
20+
21+
public static void Decrypt(byte[] data)
22+
{
23+
// Inverse of the last for-loop in Encrypt
24+
for (int index = 0; index < 8; index++)
25+
{
26+
byte val = data[index];
27+
28+
// Use reverse lookup table to find the original value
29+
data[index] = ReverseSwitchSheet[val];
30+
}
31+
32+
// Inverse of the XOR and 0x69 step
33+
data[2] = (byte)(data[2] ^ data[1] ^ 0x69);
34+
data[3] = (byte)(data[3] ^ data[1] ^ 0x69);
35+
data[4] = (byte)(data[4] ^ data[1] ^ 0x69);
36+
data[5] = (byte)(data[5] ^ data[1] ^ 0x69);
37+
data[6] = (byte)(data[6] ^ data[1] ^ 0x69);
38+
data[7] = (byte)(data[7] ^ data[1] ^ 0x69);
39+
40+
// Inverse of the bit manipulations, in reverse order
41+
if ((data[0] & 0x80) != 0)
42+
{
43+
byte saved = data[3];
44+
data[3] = (byte)((data[3] & 0xf0) | ((data[2] & 0xf0) >> 4));
45+
data[2] = (byte)((data[2] & 0xf) | ((saved & 0xf) << 4));
46+
}
47+
if ((data[0] & 0x40) != 0)
48+
{
49+
byte saved = data[3];
50+
data[3] = (byte)((data[3] & 0xf) | ((data[2] & 0xf) << 4));
51+
data[2] = (byte)((data[2] & 0xf0) | ((saved & 0xf0) >> 4));
52+
}
53+
if ((data[0] & 0x20) != 0)
54+
{
55+
byte saved = data[7];
56+
data[7] = (byte)((data[7] & 0xf0) | ((data[6] & 0xf0) >> 4));
57+
data[6] = (byte)((data[6] & 0xf) | ((saved & 0xf) << 4));
58+
}
59+
if ((data[0] & 0x10) != 0)
60+
{
61+
byte saved = data[7];
62+
data[7] = (byte)((data[7] & 0xf) | (data[5] & 0xf0));
63+
data[5] = (byte)((data[5] & 0xf) | (saved & 0xf0));
64+
}
65+
if ((data[0] & 0x08) != 0)
66+
{
67+
byte saved = data[4];
68+
data[4] = (byte)((data[4] & 0xf0) | ((data[3] & 0xf0) >> 4));
69+
data[3] = (byte)((data[3] & 0xf) | ((saved & 0xf) << 4));
70+
}
71+
if ((data[0] & 0x04) != 0)
72+
{
73+
byte saved = data[4];
74+
data[4] = (byte)((data[4] & 0xf) | (data[3] << 4));
75+
data[3] = (byte)((data[3] & 0xf0) | ((saved & 0xf0) >> 4));
76+
}
77+
if ((data[0] & 0x02) != 0)
78+
{
79+
byte saved = data[5];
80+
data[5] = (byte)((data[5] & 0xf0) | ((data[2] & 0xf0) >> 4));
81+
data[2] = (byte)((data[2] & 0xf) | ((saved & 0xf) << 4));
82+
}
83+
if ((data[0] & 0x01) != 0)
84+
{
85+
byte saved = data[6];
86+
data[6] = (byte)((data[6] & 0xf0) | (data[2] & 0xf));
87+
data[2] = (byte)((data[2] & 0xf0) | (saved & 0xf));
88+
}
89+
}
90+
}

BrickController2/BrickController2.Tools/Protocols/DecryptTools.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,12 @@ public static byte[] DecryptRfPayload(byte[] seed, int headerLength, int dataLen
5959
byte[] seedReversed = new byte[seedLength];
6060
for (int i = 0; i < headerLength; i++)
6161
{
62-
header[i] = CryptTools.Invert8(resultBuffer[headerOffset + i]);
62+
header[i] = CryptTools.Reverse(resultBuffer[headerOffset + i]);
6363
}
6464

6565
for (int i = 0; i < seedLength; i++)
6666
{
67-
seedReversed[i] = CryptTools.Invert8(resultBuffer[headerOffset + headerLength + i]);
67+
seedReversed[i] = CryptTools.Reverse(resultBuffer[headerOffset + headerLength + i]);
6868
}
6969

7070
// Reverse the seed array to get the original order

BrickController2/BrickController2/Protocols/CaDAProtocol.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public static class CaDAProtocol
4040
/// <summary>
4141
/// LookupTable
4242
/// </summary>
43-
private static readonly byte[] switchSheet = new byte[]
43+
public static readonly byte[] SwitchSheet = new byte[]
4444
{
4545
0xf4, 0xa8, 0xa0, 0x8c, 0x28, 0xec, 0x44, 0x00, 0x6c, 0x48, 0x24, 0x98, 0xd4, 0x9c, 0x0c, 0xac,
4646
0xa4, 0xbc, 0xcc, 0x80, 0x38, 0xe8, 0x5c, 0x1c, 0x94, 0xb0, 0xc8, 0x54, 0x34, 0x08, 0x74, 0xf0,
@@ -125,7 +125,7 @@ public static void Encrypt(byte[] data)
125125
data[7] = (byte)(data[7] ^ data[1] ^ 0x69);
126126
for (int index = 0; index < 8; index++)
127127
{
128-
data[index] = (byte)(switchSheet[(int)(data[index] / 4)] + data[index] % 4);
128+
data[index] = (byte)(SwitchSheet[(int)(data[index] / 4)] + data[index] % 4);
129129
}
130130
}
131131
}

BrickController2/BrickController2/Protocols/CryptTools.cs

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public static int GetRfPayload(byte[] seed, byte[] header, byte[] data, int head
5252
// invert bytes of initValues and seed-array in resultBuffer
5353
for (int index = 0; index < headerLength + seedLength; index++)
5454
{
55-
resultBuffer[headerOffset + index] = Invert8(resultBuffer[headerOffset + index]);
55+
resultBuffer[headerOffset + index] = Reverse(resultBuffer[headerOffset + index]);
5656
}
5757

5858
// copy dataArray into resultBuffer after initValues and seed-array
@@ -75,39 +75,47 @@ public static int GetRfPayload(byte[] seed, byte[] header, byte[] data, int head
7575
}
7676

7777
/// <summary>
78-
/// inverts the bits of a given byte
78+
/// Reverses the bit order of an 8-bit unsigned integer.
7979
/// </summary>
80-
/// <param name="value">byte to invert</param>
81-
/// <returns>inverted byte</returns>
82-
public static byte Invert8(byte value)
80+
/// <remarks>This method takes an 8-bit unsigned integer and reverses the order of its bits. For example,
81+
/// if the input is <c>0b00000001</c>, the output will be <c>0b10000000</c>.</remarks>
82+
/// <param name="value">The 8-bit unsigned integer whose bits are to be reversed.</param>
83+
/// <returns>An 8-bit unsigned integer with the bits of <paramref name="value"/> reversed.</returns>
84+
public static byte Reverse(byte value)
8385
{
84-
int result = 0;
85-
for (byte index = 0; index < 8; index++)
86-
{
87-
if ((value & 1 << (index & 0x1f)) != 0)
88-
{
89-
result |= (byte)(1 << (7 - index & 0x1f));
90-
}
91-
}
92-
return (byte)result;
86+
/* (bitwise swap version)
87+
// Swap odd and even bits
88+
value = (byte)(((value & 0xAA) >> 1) | ((value & 0x55) << 1));
89+
// Swap consecutive pairs
90+
value = (byte)(((value & 0xCC) >> 2) | ((value & 0x33) << 2));
91+
// Swap nibbles
92+
value = (byte)(((value & 0xF0) >> 4) | ((value & 0x0F) << 4));
93+
*/
94+
95+
// (bit-twiddling hack version)
96+
value = (byte)(((value * 0x0802U & 0x22110U) | (value * 0x8020U & 0x88440U)) * 0x10101U >> 16);
97+
return value;
9398
}
9499

95100
/// <summary>
96-
/// inverts the bits of a given short
101+
/// Reverses the bit order of a 16-bit unsigned integer.
97102
/// </summary>
98-
/// <param name="value">short to invert</param>
99-
/// <returns>inverted short</returns>
100-
public static ushort Invert16(ushort value)
103+
/// <remarks>This method swaps the positions of the bits in the input value, effectively reversing their
104+
/// order. For example, if the input value is represented in binary as <c>0000000000001011</c>, the output will be
105+
/// <c>1101000000000000</c>.</remarks>
106+
/// <param name="value">The 16-bit unsigned integer whose bits are to be reversed.</param>
107+
/// <returns>A 16-bit unsigned integer with the bit order of <paramref name="value"/> reversed.</returns>
108+
public static ushort Reverse(ushort value)
101109
{
102-
int result = 0;
103-
for (byte index = 0; index < 0x10; index++)
104-
{
105-
if (((uint)value & 1 << (index & 0x1f)) != 0)
106-
{
107-
result |= (ushort)(1 << (0xf - index & 0x1f));
108-
}
109-
}
110-
return (ushort)result;
110+
// Swap odd and even bits
111+
value = (ushort)(((value & 0xAAAA) >> 1) | ((value & 0x5555) << 1));
112+
// Swap consecutive pairs
113+
value = (ushort)(((value & 0xCCCC) >> 2) | ((value & 0x3333) << 2));
114+
// Swap nibbles
115+
value = (ushort)(((value & 0xF0F0) >> 4) | ((value & 0x0F0F) << 4));
116+
// Swap bytes
117+
value = (ushort)((value >> 8) | (value << 8));
118+
return value;
111119
}
112120

113121
/// <summary>
@@ -141,7 +149,7 @@ public static ushort CheckCRC16(byte[] array1, byte[] array2)
141149
int array2Length = array2.Length;
142150
for (int index = 0; index < array2Length; index++)
143151
{
144-
byte cVar1 = Invert8(array2[index]);
152+
byte cVar1 = Reverse(array2[index]);
145153

146154
result = result ^ (ushort)(cVar1 << 8);
147155

@@ -157,7 +165,7 @@ public static ushort CheckCRC16(byte[] array1, byte[] array2)
157165
}
158166
}
159167
}
160-
ushort result_inverse = Invert16((ushort)result);
168+
ushort result_inverse = Reverse((ushort)result);
161169
return (ushort)(result_inverse ^ 0xffff);
162170
}
163171

0 commit comments

Comments
 (0)