Skip to content

Commit

Permalink
Add new SerializeAs option for TerminatedSizedString.
Browse files Browse the repository at this point in the history
This allows to keep the existing TerminatedString behaviour when a field
length constraint is applied, where it essentially becomes a SizedString.
Whilst also allowing new behaviour that would keep the terminated string
aspect, but truncate / pad the terminated string around the length constraint

Test case shows non-standard terminator (\n or 0x0A), and non-standard
padding byte 0x0D just to prove that these work as expected also.

Possibly fixes jefffhaynes#171 (confirmation required)

Signed-off-by: Bevan Weiss <[email protected]>
  • Loading branch information
bevanweiss committed May 20, 2023
1 parent f16adc6 commit cb1369c
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 27 deletions.
19 changes: 19 additions & 0 deletions BinarySerializer.Test/SerializeAs/SerializeAsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,24 @@ public void CollectionPaddingValue()
var actualItems = actual.Items.Select(i => i.Trim()).ToList();
CollectionAssert.AreEqual(expected.Items, actualItems);
}

[TestMethod]
public void SerializeAsTerminatedStringWithPadding()
{
var expected = new TerminatedSizedStringClass { Value = "hi" };
var actual = Roundtrip(expected, new byte[] { 0x68, 0x69, 0x0A, 0x0D, 0x0D });

Assert.AreEqual(expected.Value, actual.Value);
}

[TestMethod]
public void SerializeAsTerminatedStringWithTruncation()
{
var expected = new TerminatedSizedStringClass { Value = "hi test" };
var actual = Roundtrip(expected, new byte[] { 0x68, 0x69, 0x20, 0x74, 0x0A });

// we expect to have truncated to only have 4 characters (and the terminator)
Assert.AreEqual(expected.Value.Substring(0,4), actual.Value);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace BinarySerialization.Test.SerializeAs
{
class TerminatedSizedStringClass
{
[FieldLength(5)]
[SerializeAs(SerializedType.TerminatedSizedString, StringTerminator = '\n', PaddingValue = 0x0D)]
public string Value { get; set; }
}
}
3 changes: 2 additions & 1 deletion BinarySerializer/Graph/TypeGraph/EnumTypeNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ private void InitializeEnumValues()
if (enumAttributes.Any(enumAttribute => enumAttribute.Value != null) ||
serializedType == SerializedType.TerminatedString ||
serializedType == SerializedType.SizedString ||
serializedType == SerializedType.LengthPrefixedString)
serializedType == SerializedType.LengthPrefixedString ||
serializedType == SerializedType.TerminatedSizedString)
{
EnumInfo.EnumValues = enumAttributes.ToDictionary(enumAttribute => enumAttribute.Key,
enumAttribute =>
Expand Down
7 changes: 5 additions & 2 deletions BinarySerializer/Graph/TypeGraph/TypeNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ internal abstract class TypeNode : Node<TypeNode>
{SerializedType.TerminatedString, default(string)},
{SerializedType.SizedString, default(string)},
{SerializedType.LengthPrefixedString, default(string)},
{SerializedType.TerminatedSizedString, default(string)},
{SerializedType.ByteArray, default(byte[])}
};

Expand Down Expand Up @@ -154,7 +155,8 @@ protected TypeNode(TypeNode parent, Type parentType, MemberInfo memberInfo, Type
}
#pragma warning restore 618

if (_serializedType.Value == SerializedType.TerminatedString)
if (_serializedType.Value == SerializedType.TerminatedString ||
_serializedType.Value == SerializedType.TerminatedSizedString)
{
AreStringsTerminated = true;
StringTerminator = serializeAsAttribute.StringTerminator;
Expand All @@ -170,7 +172,8 @@ protected TypeNode(TypeNode parent, Type parentType, MemberInfo memberInfo, Type
IsNullable = serializedType == SerializedType.Default ||
serializedType == SerializedType.ByteArray ||
serializedType == SerializedType.TerminatedString ||
serializedType == SerializedType.SizedString;
serializedType == SerializedType.SizedString ||
serializedType == SerializedType.TerminatedSizedString;
}

// setup bindings
Expand Down
98 changes: 75 additions & 23 deletions BinarySerializer/Graph/ValueGraph/ValueValueNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,16 +133,19 @@ public void Serialize(AsyncBinaryWriter writer, object value, SerializedType ser

// see if we're dealing with something that needs to be padded
if (serializedType != SerializedType.ByteArray &&
serializedType != SerializedType.TerminatedString)
serializedType != SerializedType.TerminatedString &&
serializedType != SerializedType.TerminatedSizedString)
{
return;
}

var data = new byte[constLength.TotalByteCount];

if (serializedType == SerializedType.TerminatedString && data.Length > 0)
if ((serializedType == SerializedType.TerminatedString ||
serializedType == SerializedType.TerminatedSizedString)
&& data.Length > 0)
{
var terminatorData = GetNullTermination();
var terminatorData = GetTermination();
Array.Copy(terminatorData, data, terminatorData.Length);
}

Expand Down Expand Up @@ -224,7 +227,7 @@ public void Serialize(AsyncBinaryWriter writer, object value, SerializedType ser
var encoding = GetFieldEncoding();
var data = encoding.GetBytes(value.ToString());

var dataLength = GetNullTerminatedArrayLength(data, constLength, maxLength);
var dataLength = GetTerminatedArrayLength(data, constLength, maxLength);

writer.Write(data, dataLength);

Expand All @@ -234,7 +237,8 @@ public void Serialize(AsyncBinaryWriter writer, object value, SerializedType ser
}
case SerializedType.SizedString:
{
var data = GetFieldEncoding().GetBytes(value.ToString());
var encoding = GetFieldEncoding();
var data = encoding.GetBytes(value.ToString());

var dataLength = GetArrayLength(data, constLength, maxLength);

Expand All @@ -251,6 +255,25 @@ public void Serialize(AsyncBinaryWriter writer, object value, SerializedType ser
writer.Write(value.ToString());
break;
}
case SerializedType.TerminatedSizedString:
{
var encoding = GetFieldEncoding();
var data = encoding.GetBytes(value.ToString());

var dataLength = GetTerminatedArrayLength(data, constLength, maxLength);

writer.Write(data, dataLength);

var stringTerminatorData = encoding.GetBytes(new[] { TypeNode.StringTerminator });
writer.Write(stringTerminatorData, stringTerminatorData.Length);

var fillLength = maxLength < constLength ? maxLength : constLength;
var padLength = fillLength - (dataLength + stringTerminatorData.Length);
if (padLength > 0)
writer.Write(GetFieldPaddingValue(), padLength);

break;
}

default:
throw new NotSupportedException(TypeNotSupportedMessage);
Expand Down Expand Up @@ -286,16 +309,19 @@ public async Task SerializeAsync(AsyncBinaryWriter writer, object value, Seriali

// see if we're dealing with something that needs to be padded
if (serializedType != SerializedType.ByteArray &&
serializedType != SerializedType.TerminatedString)
serializedType != SerializedType.TerminatedString &&
serializedType != SerializedType.TerminatedSizedString)
{
return;
}

var data = new byte[constLength.TotalByteCount];

if (serializedType == SerializedType.TerminatedString && data.Length > 0)
if ((serializedType == SerializedType.TerminatedString ||
serializedType == SerializedType.TerminatedSizedString)
&& data.Length > 0)
{
var terminatorData = GetNullTermination();
var terminatorData = GetTermination();
Array.Copy(terminatorData, data, terminatorData.Length);
}

Expand Down Expand Up @@ -376,11 +402,11 @@ public async Task SerializeAsync(AsyncBinaryWriter writer, object value, Seriali
{
var data = GetFieldEncoding().GetBytes(value.ToString());

var dataLength = GetNullTerminatedArrayLength(data, constLength, maxLength);
var dataLength = GetTerminatedArrayLength(data, constLength, maxLength);

await writer.WriteAsync(data, dataLength, cancellationToken).ConfigureAwait(false);

var stringTerminatorData = GetNullTermination();
var stringTerminatorData = GetTermination();
await writer.WriteAsync(TypeNode.StringTerminator, stringTerminatorData.Length, cancellationToken)
.ConfigureAwait(false);

Expand All @@ -404,6 +430,25 @@ await writer.WriteAsync(TypeNode.StringTerminator, stringTerminatorData.Length,
writer.Write(value.ToString());
break;
}
case SerializedType.TerminatedSizedString:
{
var encoding = GetFieldEncoding();
var data = encoding.GetBytes(value.ToString());

var dataLength = GetTerminatedArrayLength(data, constLength, maxLength);

await writer.WriteAsync(data, dataLength, cancellationToken);

var stringTerminatorData = encoding.GetBytes(new[] { TypeNode.StringTerminator });
await writer.WriteAsync(stringTerminatorData, stringTerminatorData.Length, cancellationToken);

var fillLength = maxLength < constLength ? maxLength : constLength;
var padLength = fillLength - (dataLength + stringTerminatorData.Length);
if (padLength > 0)
await writer.WriteAsync(GetFieldPaddingValue(), padLength, cancellationToken);

break;
}

default:

Expand Down Expand Up @@ -467,7 +512,8 @@ public void Deserialize(BinaryReader reader, SerializedType serializedType, Fiel
break;
}
case SerializedType.TerminatedString:
{
case SerializedType.TerminatedSizedString:
{
var data = ReadTerminated(reader, effectiveLengthValue, TypeNode.StringTerminator);
value = GetFieldEncoding().GetString(data, 0, data.Length);
break;
Expand Down Expand Up @@ -540,7 +586,8 @@ public async Task DeserializeAsync(AsyncBinaryReader reader, SerializedType seri
break;
}
case SerializedType.TerminatedString:
{
case SerializedType.TerminatedSizedString:
{
var data = await ReadTerminatedAsync(reader, (int) effectiveLengthValue.ByteCount, TypeNode.StringTerminator,
cancellationToken)
.ConfigureAwait(false);
Expand Down Expand Up @@ -675,7 +722,7 @@ private FieldLength GetConstLength(FieldLength length)
return constLength;
}

private byte[] GetNullTermination()
private byte[] GetTermination()
{
return GetFieldEncoding().GetBytes(new[] { TypeNode.StringTerminator });
}
Expand All @@ -684,8 +731,10 @@ private static FieldLength GetMaxLength(BoundedStream stream, SerializedType ser
{
FieldLength maxLength = null;

if (serializedType == SerializedType.ByteArray || serializedType == SerializedType.SizedString ||
serializedType == SerializedType.TerminatedString)
if (serializedType == SerializedType.ByteArray ||
serializedType == SerializedType.SizedString ||
serializedType == SerializedType.TerminatedString ||
serializedType == SerializedType.TerminatedSizedString)
{
// try to get bounded length
maxLength = stream.AvailableForWriting;
Expand All @@ -711,20 +760,22 @@ private static FieldLength GetArrayLength(byte[] data, FieldLength constLength,
return length;
}

private FieldLength GetNullTerminatedArrayLength(byte[] data, FieldLength constLength, FieldLength maxLength)
private FieldLength GetTerminatedArrayLength(byte[] data, FieldLength constLength, FieldLength maxLength)
{
FieldLength length = data.Length;
var nullTermination = GetNullTermination();
var nullTerminationLength = new FieldLength(nullTermination.Length);
var termination = GetTermination();
var terminationLength = new FieldLength(termination.Length);

if (constLength != null)
if (constLength != null &&
constLength < length + terminationLength)
{
length = constLength - nullTerminationLength;
length = constLength - terminationLength;
}

if (maxLength != null && length > maxLength)
if (maxLength != null &&
maxLength < length + terminationLength)
{
length = maxLength - nullTerminationLength;
length = maxLength - terminationLength;
}

return length;
Expand Down Expand Up @@ -776,7 +827,8 @@ private FieldLength GetEffectiveLengthValue(BinaryReader reader, SerializedType
{
if (serializedType == SerializedType.ByteArray ||
serializedType == SerializedType.SizedString ||
serializedType == SerializedType.TerminatedString)
serializedType == SerializedType.TerminatedString ||
serializedType == SerializedType.TerminatedSizedString)
{
// try to get bounded length
var baseStream = (BoundedStream) reader.BaseStream;
Expand Down
8 changes: 7 additions & 1 deletion BinarySerializer/SerializedType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ public enum SerializedType
/// <summary>
/// An encoded string prefixed with a LEB128-encoded length. This is equivalent to how BinaryWriter encodes a string.
/// </summary>
LengthPrefixedString
LengthPrefixedString,

/// <summary>
/// An encoded string with a terminator, which is null (zero) by default or can be specified by setting StringTerminator.
/// Also has a maximum length storage available
/// </summary>
TerminatedSizedString,
}
}

0 comments on commit cb1369c

Please sign in to comment.