Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

experimental spike for SO66453263 - zero padding #800

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
55 changes: 55 additions & 0 deletions src/Examples/Issues/SO66453263.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using ProtoBuf;
using System.IO;
using Xunit;

namespace Examples.Issues
{
public class SO66453263 // handling zero-padded data
{

static MemoryStream GetZeroPaddedData()
{
var ms = new MemoryStream();
Serializer.Serialize(ms, new Foo { Id = 42, Name = "abc" });
for (int i = 0; i < 10; i++) ms.WriteByte(0); // add some zero padding
ms.Position = 0;
return ms;
}

[Fact]
public void ZeroPaddingFailsByDefault()
{
using var ms = GetZeroPaddedData();
var ex = Assert.Throws<ProtoException>(() => Serializer.Deserialize<Foo>(ms));
Assert.Equal("Invalid field in source data: 0", ex.Message);
}

[Fact]
public void ZeroPaddingWorksWithCustomStateContext()
{
using var ms = GetZeroPaddedData();
var obj = Serializer.Deserialize<Foo>(ms, userState: MyCustomState.Instance);
Assert.Equal(42, obj.Id);
Assert.Equal("abc", obj.Name);
}

class MyCustomState : ISerializationOptions
{
public static MyCustomState Instance { get; } = new MyCustomState();

SerializationOptions ISerializationOptions.Options => SerializationOptions.AllowZeroPadding;

private MyCustomState() { }
}

[ProtoContract]
public class Foo
{
[ProtoMember(1)]
public int Id { get; set; }

[ProtoMember(2)]
public string Name { get; set; }
}
}
}
32 changes: 32 additions & 0 deletions src/protobuf-net.Core/ISerializationOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;

namespace ProtoBuf
{
/// <summary>
/// Describes additional optional serialization behaviours
/// </summary>
public interface ISerializationOptions
{
/// <summary>
/// Gets the additional options to consider for this operation
/// </summary>
SerializationOptions Options { get; }
}

/// <summary>
/// Describes additional optional serialization behaviours
/// </summary>
[Flags]
public enum SerializationOptions
{
/// <summary>
/// No additional options
/// </summary>
None = 0,

/// <summary>
/// Interpret trailing zeros in data as EOF, rather than throwing an error
/// </summary>
AllowZeroPadding = 1 << 0,
}
}
2 changes: 1 addition & 1 deletion src/protobuf-net.Core/ProtoReader.State.ReadMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -741,7 +741,7 @@ public bool TryReadFieldHeader(int field)
[MethodImpl(HotPath)]
internal void CheckFullyConsumed()
{
if (!_reader.IsFullyConsumed(ref this)) ThrowProtoException("Incorrect number of bytes consumed");
if (!_reader.IsFullyConsumed(ref this) && !_reader.AllowZeroPadding) ThrowProtoException("Incorrect number of bytes consumed");
}

/// <summary>
Expand Down
14 changes: 11 additions & 3 deletions src/protobuf-net.Core/ProtoReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -305,16 +305,24 @@ public static SubItemToken StartSubItem(ProtoReader reader)
[MethodImpl(HotPath)]
private int SetTag(uint tag)
{
if ((_fieldNumber = (int)(tag >> 3)) < 1) ThrowInvalidField(_fieldNumber);
if ((_fieldNumber = (int)(tag >> 3)) < 1) ThrowInvalidField();
if ((WireType = (WireType)(tag & 7)) == WireType.EndGroup)
{
if (_depth > 0) return 0; // spoof an end, but note we still set the field-number
ThrowUnexpectedEndGroup();
}
return _fieldNumber;
}
private static void ThrowInvalidField(int fieldNumber)
=> ThrowHelper.ThrowProtoException("Invalid field in source data: " + fieldNumber.ToString());

private bool AllowZeroPadding => UserState is ISerializationOptions options && (options.Options & SerializationOptions.AllowZeroPadding) != 0;

private void ThrowInvalidField()
{
if (!(_fieldNumber == 0 && AllowZeroPadding))
{
ThrowHelper.ThrowProtoException("Invalid field in source data: " + _fieldNumber.ToString());
}
}
private static void ThrowUnexpectedEndGroup()
=> ThrowHelper.ThrowProtoException("Unexpected end-group in source data; this usually means the source data is corrupt");

Expand Down