Skip to content

Commit 9cfc7b6

Browse files
committed
Refactor BinarySerializationProtocol
1 parent 104e204 commit 9cfc7b6

File tree

3 files changed

+77
-32
lines changed

3 files changed

+77
-32
lines changed

src/NUnitEngine/nunit.engine.core.tests/Communication/Protocols/BinarySerializationProtocolTests.cs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ public void DecodeSingleMessage()
5050

5151
Assert.That(message.Code, Is.EqualTo(MessageCode.CommandResult));
5252
Assert.That(message.Data, Is.EqualTo(originalPackage.ToXml()));
53-
//var newPackage = DeserializePackage(message.Data);
54-
//ComparePackages(newPackage, originalPackage);
53+
var newPackage = new TestPackage().FromXml(message.Data);
54+
ComparePackages(newPackage, originalPackage);
5555
}
5656

5757
[TestCase(1)]
@@ -88,8 +88,8 @@ public void DecodeSplitMessages(int numMessages)
8888
foreach (TestEngineMessage message in messages)
8989
{
9090
Assert.That(message.Code, Is.EqualTo(MessageCode.CommandResult));
91-
//var newPackage = DeserializePackage(message.Data);
92-
//ComparePackages(newPackage, originalPackage);
91+
var newPackage = new TestPackage().FromXml(message.Data);
92+
ComparePackages(newPackage, originalPackage);
9393
}
9494
}
9595

@@ -113,5 +113,24 @@ public void DecodeMultipleMessages()
113113
Assert.That(received[i].Code, Is.EqualTo(commands[i]));
114114
}
115115

116+
private void ComparePackages(TestPackage newPackage, TestPackage oldPackage)
117+
{
118+
Assert.Multiple(() =>
119+
{
120+
Assert.That(newPackage.Name, Is.EqualTo(oldPackage.Name));
121+
Assert.That(newPackage.FullName, Is.EqualTo(oldPackage.FullName));
122+
Assert.That(newPackage.Settings.Count, Is.EqualTo(oldPackage.Settings.Count));
123+
Assert.That(newPackage.SubPackages.Count, Is.EqualTo(oldPackage.SubPackages.Count));
124+
125+
foreach (var key in oldPackage.Settings.Keys)
126+
{
127+
Assert.That(newPackage.Settings.ContainsKey(key));
128+
Assert.That(newPackage.Settings[key], Is.EqualTo(oldPackage.Settings[key]));
129+
}
130+
131+
for (int i = 0; i < oldPackage.SubPackages.Count; i++)
132+
ComparePackages(newPackage.SubPackages[i], oldPackage.SubPackages[i]);
133+
});
134+
}
116135
}
117136
}

src/NUnitEngine/nunit.engine.core/Communication/Protocols/BinarySerializationProtocol.cs

Lines changed: 53 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,29 @@
55
using System.IO;
66
using System.Runtime.Serialization;
77
using System.Runtime.Serialization.Formatters.Binary;
8+
using System.Text;
89
using NUnit.Engine.Communication.Messages;
910

1011
namespace NUnit.Engine.Communication.Protocols
1112
{
1213
/// <summary>
13-
/// BinarySerializationProtocol serializes messages in the following format:
14+
/// BinarySerializationProtocol serializes messages as an array of bytes in the following format:
1415
///
15-
/// [Message Length (4 bytes)][Serialized Message Content]
16-
///
17-
/// The message content is in binary form as produced by the .NET BinaryFormatter.
18-
/// Each message of length n is serialized as n + 4 bytes.
16+
/// <Message Length> <Message Code> [<Additional Message Data>]
17+
///
18+
/// The length of the message is encoded as four bytes, from lowest to highest order.
19+
///
20+
/// The message code is always four bytes and indicates the type of message.
21+
///
22+
/// Messages taking an additional data argument encode it in the remaining bytes. The
23+
/// argument length may therefore be calculated as overall length - 8 bytes. Messages
24+
/// without an argument are serialized as 8 bytes.
1925
/// </summary>
2026
public class BinarySerializationProtocol : ISerializationProtocol
2127
{
28+
private const int MSG_LENGTH_SIZE = 4;
29+
private const int MSG_CODE_SIZE = 4;
30+
2231
/// <summary>
2332
/// Maximum length of a message.
2433
/// </summary>
@@ -34,24 +43,30 @@ public class BinarySerializationProtocol : ISerializationProtocol
3443
/// </summary>
3544
/// <param name="message">Message to be serialized</param>
3645
/// <returns>A byte[] containing the message, serialized as per the protocol.</returns>
37-
public byte[] Encode(object message)
46+
public byte[] Encode(TestEngineMessage message)
3847
{
39-
//Serialize the message to a byte array
40-
var serializedMessage = SerializeMessage(message);
48+
string code = message.Code;
49+
string data = message.Data;
50+
Encoding utf8 = Encoding.UTF8;
51+
52+
// TODO: Compile time check
53+
if (utf8.GetByteCount(code) != MSG_CODE_SIZE)
54+
throw new ArgumentException($"Invalid message code {code}");
55+
56+
int dataLength = message.Data != null ? utf8.GetByteCount(message.Data) : 0;
57+
int messageLength = dataLength + MSG_CODE_SIZE;
4158

42-
//Check for message length
43-
var messageLength = serializedMessage.Length;
59+
//Check message length
4460
if (messageLength > MAX_MESSAGE_LENGTH)
45-
{
4661
throw new Exception("Message is too big (" + messageLength + " bytes). Max allowed length is " + MAX_MESSAGE_LENGTH + " bytes.");
47-
}
4862

4963
//Create a byte array including the length of the message (4 bytes) and serialized message content
50-
var bytes = new byte[messageLength + 4];
64+
var bytes = new byte[messageLength + MSG_LENGTH_SIZE];
5165
WriteInt32(bytes, 0, messageLength);
52-
Array.Copy(serializedMessage, 0, bytes, 4, messageLength);
66+
utf8.GetBytes(code, 0, code.Length, bytes, MSG_LENGTH_SIZE);
67+
if (dataLength > 0)
68+
utf8.GetBytes(data, 0, data.Length, bytes, MSG_LENGTH_SIZE + MSG_CODE_SIZE);
5369

54-
//Return serialized message by this protocol
5570
return bytes;
5671
}
5772

@@ -139,23 +154,21 @@ private bool ReadSingleMessage(ICollection<TestEngineMessage> messages)
139154

140155
//If stream has less than 4 bytes, that means we can not even read length of the message
141156
//So, return false to wait more for bytes from remorte application.
142-
if (_receiveMemoryStream.Length < 4)
157+
if (_receiveMemoryStream.Length < MSG_LENGTH_SIZE)
143158
{
144159
return false;
145160
}
146161

147162
//Read length of the message
148163
var messageLength = ReadInt32(_receiveMemoryStream);
149164
if (messageLength > MAX_MESSAGE_LENGTH)
150-
{
151165
throw new Exception("Message is too big (" + messageLength + " bytes). Max allowed length is " + MAX_MESSAGE_LENGTH + " bytes.");
152-
}
153166

154167
//If message is zero-length (It must not be but good approach to check it)
155168
if (messageLength == 0)
156169
{
157170
//if no more bytes, return immediately
158-
if (_receiveMemoryStream.Length == 4)
171+
if (_receiveMemoryStream.Length == MSG_LENGTH_SIZE)
159172
{
160173
_receiveMemoryStream = new MemoryStream(); //Clear the stream
161174
return false;
@@ -164,12 +177,12 @@ private bool ReadSingleMessage(ICollection<TestEngineMessage> messages)
164177
//Create a new memory stream from current except first 4-bytes.
165178
var bytes = _receiveMemoryStream.ToArray();
166179
_receiveMemoryStream = new MemoryStream();
167-
_receiveMemoryStream.Write(bytes, 4, bytes.Length - 4);
180+
_receiveMemoryStream.Write(bytes, MSG_LENGTH_SIZE, bytes.Length - MSG_LENGTH_SIZE);
168181
return true;
169182
}
170183

171184
//If all bytes of the message is not received yet, return to wait more bytes
172-
if (_receiveMemoryStream.Length < (4 + messageLength))
185+
if (_receiveMemoryStream.Length < (MSG_LENGTH_SIZE + messageLength))
173186
{
174187
_receiveMemoryStream.Position = _receiveMemoryStream.Length;
175188
return false;
@@ -178,7 +191,15 @@ private bool ReadSingleMessage(ICollection<TestEngineMessage> messages)
178191
//Read bytes of serialized message and deserialize it
179192
var serializedMessageBytes = ReadByteArray(_receiveMemoryStream, messageLength);
180193

181-
messages.Add((TestEngineMessage)DeserializeMessage(serializedMessageBytes));
194+
Encoding utf8 = Encoding.UTF8;
195+
196+
string code = utf8.GetString(serializedMessageBytes, 0, MSG_CODE_SIZE);
197+
198+
string data = messageLength > MSG_CODE_SIZE
199+
? utf8.GetString(serializedMessageBytes, MSG_CODE_SIZE, messageLength - MSG_CODE_SIZE)
200+
: null;
201+
202+
messages.Add(new TestEngineMessage(code, data));
182203

183204
//Read remaining bytes to an array
184205
var remainingBytes = ReadByteArray(_receiveMemoryStream, (int)(_receiveMemoryStream.Length - (4 + messageLength)));
@@ -188,7 +209,7 @@ private bool ReadSingleMessage(ICollection<TestEngineMessage> messages)
188209
_receiveMemoryStream.Write(remainingBytes, 0, remainingBytes.Length);
189210

190211
//Return true to re-call this method to try to read next message
191-
return (remainingBytes.Length > 4);
212+
return (remainingBytes.Length > MSG_LENGTH_SIZE);
192213
}
193214

194215
/// <summary>
@@ -212,10 +233,15 @@ private static void WriteInt32(byte[] buffer, int startIndex, int number)
212233
private static int ReadInt32(Stream stream)
213234
{
214235
var buffer = ReadByteArray(stream, 4);
215-
return ((buffer[0] << 24) |
216-
(buffer[1] << 16) |
217-
(buffer[2] << 8) |
218-
(buffer[3])
236+
return ReadInt32(buffer, 0);
237+
}
238+
239+
private static int ReadInt32(byte[] buffer, int index)
240+
{
241+
return ((buffer[index] << 24) |
242+
(buffer[index + 1] << 16) |
243+
(buffer[index + 2] << 8) |
244+
(buffer[index + 3])
219245
);
220246
}
221247

src/NUnitEngine/nunit.engine.core/Communication/Protocols/ISerializationProtocol.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public interface ISerializationProtocol
1212
/// </summary>
1313
/// <param name="message">Message to be serialized</param>
1414
/// <returns>A byte[] containing the message, serialized as per the protocol.</returns>
15-
byte[] Encode(object message);
15+
byte[] Encode(TestEngineMessage message);
1616

1717
/// <summary>
1818
/// Accept an array of bytes and deserialize the messages that are found.

0 commit comments

Comments
 (0)