Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions BinarySerializer.Test/Offset/BoundOffsetCurrentNoRewindClass.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.IO;

namespace BinarySerialization.Test.Offset
{
public class BoundOffsetCurrentNoRewindClass
{
[FieldOrder(0)]
public uint FieldOffsetField { get; set; }

[FieldOrder(1)]
[FieldOffset(nameof(FieldOffsetField), SeekOrigin.Current, false)]
public uint Field { get; set; }

[FieldOrder(2)]
public uint LastUInt { get; set; }
}
}
28 changes: 28 additions & 0 deletions BinarySerializer.Test/Offset/BoundOffsetJumpyNoRewindClass.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.IO;

namespace BinarySerialization.Test.Offset
{
public class BoundOffsetJumpyNoRewindClass
{
[FieldOrder(0)]
public uint FieldOffsetField1 { get; set; }

[FieldOrder(1)]
[FieldOffset(nameof(FieldOffsetField1), false)]
public uint Field1 { get; set; }

[FieldOrder(2)]
public uint FieldOffsetField2 { get; set; }

[FieldOrder(3)]
[FieldOffset(nameof(FieldOffsetField2), false)]
public uint Field2 { get; set; }

[FieldOrder(4)]
public uint FieldOffsetField3 { get; set; }

[FieldOrder(5)]
[FieldOffset(nameof(FieldOffsetField3), SeekOrigin.Current, false)]
public uint Field3 { get; set; }
}
}
19 changes: 19 additions & 0 deletions BinarySerializer.Test/Offset/OffsetTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,24 @@ public void BoundOffsetTest()
var actual = Roundtrip(expected, expected.FieldOffsetField + expected.Field.Length + 1);
Assert.AreEqual(expected.Field, actual.Field);
}

[TestMethod]
public void BoundOffsetCurrentNoRewindTest()
{
var expected = new BoundOffsetCurrentNoRewindClass { FieldOffsetField = 50, Field = 404, LastUInt = 9};
var actual = Roundtrip(expected, sizeof(uint) + expected.FieldOffsetField + sizeof(uint) + sizeof(uint));
Assert.AreEqual(expected.Field, actual.Field);
Assert.AreEqual(expected.LastUInt, actual.LastUInt);
}

[TestMethod]
public void BoundOffsetJumpyNoRewindTest()
{
var expected = new BoundOffsetJumpyNoRewindClass { FieldOffsetField1 = 100, Field1 = 104, FieldOffsetField2 = 200, Field2 = 204, FieldOffsetField3 = sizeof(uint), Field3 = 304 };
var actual = Roundtrip(expected, expected.FieldOffsetField2 + sizeof(uint) + sizeof(uint) + expected.FieldOffsetField3 + sizeof(uint));
Assert.AreEqual(expected.Field1, actual.Field1);
Assert.AreEqual(expected.Field2, actual.Field2);
Assert.AreEqual(expected.Field3, actual.Field3);
}
}
}
47 changes: 43 additions & 4 deletions BinarySerializer/FieldOffsetAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.IO;

namespace BinarySerialization
{
Expand All @@ -11,17 +12,45 @@ public sealed class FieldOffsetAttribute : FieldBindingBaseAttribute, IConstAttr
/// <summary>
/// Initializes a new instance of the FieldOffset attribute with a fixed offset.
/// </summary>
/// <param name="offset"></param>
public FieldOffsetAttribute(ulong offset)
/// <param name="offset">Offset position from <see cref="SeekOrigin"/></param>
/// <param name="seekOrigin">Specifies the position in a stream to use for seeking the field</param>
/// <param name="rewind">If true it will seek back to position where it was before seek, otherwise stream will continue from the current position</param>
public FieldOffsetAttribute(ulong offset, SeekOrigin seekOrigin = SeekOrigin.Begin, bool rewind = true)
{
ConstOffset = offset;
SeekOrigin = seekOrigin;
Rewind = rewind;
}

/// <summary>
/// Initializes a new instance of the FieldOffset attribute with a fixed offset.
/// </summary>
/// <param name="offset">Offset position from <see cref="SeekOrigin"/></param>
/// <param name="rewind">If true it will seek back to position where it was before seek, otherwise stream will continue from the current position</param>
/// <param name="seekOrigin">Specifies the position in a stream to use for seeking the field</param>
public FieldOffsetAttribute(ulong offset, bool rewind, SeekOrigin seekOrigin = SeekOrigin.Begin) : this(offset, seekOrigin, rewind)
{
}

/// <summary>
/// Initializes a new instance of the FieldOffset attribute with a path pointing to a source binding member.
/// </summary>
/// <param name="path">A path to the source member</param>
/// <param name="seekOrigin">Specifies the position in a stream to use for seeking the field</param>
/// <param name="rewind">If true it will seek back to position where it was before seek, otherwise stream will continue from the current position</param>
public FieldOffsetAttribute(string path, SeekOrigin seekOrigin = SeekOrigin.Begin, bool rewind = true) : base(path)
{
SeekOrigin = seekOrigin;
Rewind = rewind;
}

/// <summary>
/// Initializes a new instance of the FieldOffset attribute with a path pointing to a source binding member.
/// </summary>
/// <param name="path">A path to the source member.</param>
public FieldOffsetAttribute(string path) : base(path)
/// <param name="path">A path to the source member</param>
/// <param name="rewind">If true it will seek back to position where it was before seek, otherwise stream will continue from the current position</param>
/// <param name="seekOrigin">Specifies the position in a stream to use for seeking the field</param>
public FieldOffsetAttribute(string path, bool rewind, SeekOrigin seekOrigin = SeekOrigin.Begin) : this(path, seekOrigin, rewind)
{
}

Expand All @@ -37,5 +66,15 @@ public object GetConstValue()
{
return ConstOffset;
}

/// <summary>
/// Specifies the position in a stream to use for seeking the field
/// </summary>
public SeekOrigin SeekOrigin { get; set; }

/// <summary>
/// If true it will seek back to position where it was before seek, otherwise stream will continue from the current position
/// </summary>
public bool Rewind { get; set; }
}
}
11 changes: 11 additions & 0 deletions BinarySerializer/Graph/TypeGraph/TypeNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
Expand Down Expand Up @@ -141,6 +142,13 @@ protected TypeNode(TypeNode parent, Type parentType, MemberInfo memberInfo, Type
Order = fieldOrderAttribute.Order;
}

var offsetAttribute = attributes.OfType<FieldOffsetAttribute>().SingleOrDefault();
if (offsetAttribute != null)
{
OffsetSeekOrigin = offsetAttribute.SeekOrigin;
OffsetRewind = offsetAttribute.Rewind;
}

var serializeAsAttribute = attributes.OfType<SerializeAsAttribute>().SingleOrDefault();
if (serializeAsAttribute != null)
{
Expand Down Expand Up @@ -355,6 +363,9 @@ protected TypeNode(TypeNode parent, Type parentType, MemberInfo memberInfo, Type

public int? Order { get; }

public SeekOrigin OffsetSeekOrigin { get; } = SeekOrigin.Begin;
public bool OffsetRewind { get; } = true;

public bool AreStringsTerminated { get; }

public char StringTerminator { get; }
Expand Down
50 changes: 42 additions & 8 deletions BinarySerializer/Graph/ValueGraph/ValueNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -201,9 +201,17 @@ internal void Serialize(BoundedStream stream, EventShuttle eventShuttle, bool al

if (offset != null)
{
using (new StreamResetter(stream))
if (TypeNode.OffsetRewind)
{
stream.Position = offset.Value;
using (new StreamResetter(stream))
{
stream.Seek(offset.Value, TypeNode.OffsetSeekOrigin);
SerializeInternal(stream, GetConstFieldLength, eventShuttle, measuring);
}
}
else
{
stream.Seek(offset.Value, TypeNode.OffsetSeekOrigin);
SerializeInternal(stream, GetConstFieldLength, eventShuttle, measuring);
Comment thread
sn4k3 marked this conversation as resolved.
Outdated
}
}
Expand Down Expand Up @@ -255,9 +263,18 @@ internal async Task SerializeAsync(BoundedStream stream, EventShuttle eventShutt

if (offset != null)
{
using (new StreamResetter(stream))
if (TypeNode.OffsetRewind)
{
using (new StreamResetter(stream))
{
stream.Seek(offset.Value, TypeNode.OffsetSeekOrigin);
await SerializeInternalAsync(stream, GetConstFieldLength, eventShuttle, cancellationToken)
.ConfigureAwait(false);
}
}
else
{
stream.Position = offset.Value;
stream.Seek(offset.Value, TypeNode.OffsetSeekOrigin);
await SerializeInternalAsync(stream, GetConstFieldLength, eventShuttle, cancellationToken)
.ConfigureAwait(false);
}
Expand Down Expand Up @@ -317,9 +334,17 @@ internal void Deserialize(BoundedStream stream, EventShuttle eventShuttle)

if (offset != null)
{
using (new StreamResetter(stream))
if (TypeNode.OffsetRewind)
{
stream.Position = offset.Value;
using (new StreamResetter(stream))
{
stream.Seek(offset.Value, TypeNode.OffsetSeekOrigin);
DeserializeInternal(stream, GetFieldLength, eventShuttle);
}
}
else
{
stream.Seek(offset.Value, TypeNode.OffsetSeekOrigin);
DeserializeInternal(stream, GetFieldLength, eventShuttle);
}
}
Expand Down Expand Up @@ -368,9 +393,18 @@ internal async Task DeserializeAsync(BoundedStream stream, EventShuttle eventShu

if (offset != null)
{
using (new StreamResetter(stream))
if (TypeNode.OffsetRewind)
{
using (new StreamResetter(stream))
{
stream.Seek(offset.Value, TypeNode.OffsetSeekOrigin);
await DeserializeInternalAsync(stream, GetFieldLength, eventShuttle, cancellationToken)
.ConfigureAwait(false);
}
}
else
{
stream.Position = offset.Value;
stream.Seek(offset.Value, TypeNode.OffsetSeekOrigin);
await DeserializeInternalAsync(stream, GetFieldLength, eventShuttle, cancellationToken)
.ConfigureAwait(false);
}
Expand Down
70 changes: 69 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,75 @@ The FieldCrc32 is identical to the FieldCrc16 with the difference that it operat

### FieldOffsetAttribute ###

The FieldOffset attribute should be used sparingly but can be used if an absolute offset is required. In most cases implicit offset (e.g. just define the structure) is preferable. After moving to the offset the serializer will reset to the origin so subsequent fields must manage their own offsets. This attribute is not supported when serializing to non-seekable streams.
The FieldOffset attribute should be used sparingly but can be used if an absolute(default) or incremental offset is required.
In most cases implicit offset (e.g. just define the structure) is preferable.
By default and after moving to the offset the serializer will reset to the origin so subsequent fields must manage their own offsets, otherwise set `Rewind` to `false`.
This attribute is not supported when serializing to non-seekable streams.

**Note:** `SeekOrigin.End` is highly inadvisable since you don't have the stream total size while serialization.
If you want to use the `SeekOrigin.End` make sure it's only to deserialize a object or else you will need to set the correct size first with `stream.SetLength()`.

```c#
public class FileSpec
{
[FieldOrder(0)]
[FieldLength(12)]
[SerializeAs(SerializedType.TerminatedString)]
public string FileVersion { get; set; } = "FileMarking";

[FieldOrder(1)]
public uint FileVersion { get; set; }

[FieldOrder(2)]
public uint HeaderAddress { get; set; }

[FieldOrder(3)]
public uint SettingsAddress { get; set; }

[FieldOrder(4)]
public uint PreviewAddress { get; set; }

[FieldOrder(5)]
public uint LayersAddress { get; set; }

[FieldOrder(6)]
[FieldOffset(nameof(HeaderAddress), false)]
public uint HeaderField1 { get; set; }

[FieldOrder(7)]
public uint HeaderField2 { get; set; }

[FieldOrder(8)]
public uint HeaderField3 { get; set; }

[FieldOrder(9)]
[FieldOffset(nameof(SettingsAddress), false)]
public uint SettingsField1 { get; set; }

[FieldOrder(10)]
public uint SettingsField2 { get; set; }

[FieldOrder(11)]
[FieldOffset(nameof(PreviewAddress), false)]
public uint PreviewSize { get; set; }

[FieldOrder(12)]
[FieldCount(nameof(PreviewSize))]
public byte[] PreviewData {get; set; }

[FieldOrder(13)]
[FieldOffset(nameof(LayersAddress), false)]
public uint LayerCount { get; set; }

[FieldOrder(14)]
[FieldCount(nameof(LayerCount))]
public Layer[] Layers { get; set; }

[FieldOrder(15)]
[FieldOffset(4, SeekOrigin.Current, false)] // Skip unused 4 bytes first, same as declaring uint Padding
public uint FileChecksum { get; set; }
}
```

### SubtypeAttribute ###

Expand Down