Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
106 changes: 106 additions & 0 deletions PowerKit.Tests/BinaryReaderExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
using System;
using System.IO;
using System.Text;
using FluentAssertions;
using PowerKit.Extensions;
using Xunit;

namespace PowerKit.Tests;

public class BinaryReaderExtensionsTests
{
[Fact]
public void SkipZeroes_NonSeekableStream_Test()
{
// Arrange
var data = new byte[] { 0, 0, 1, 2 };
using var stream = new NonSeekableStream(data);
using var reader = new BinaryReader(stream);

// Act & assert
var act = () => reader.SkipZeroes();
act.Should().Throw<InvalidOperationException>();
}

[Fact]
public void SkipZeroes_Test()
{
// Arrange
var data = new byte[] { 0, 0, 0, 1, 2, 3 };
using var stream = new MemoryStream(data);
using var reader = new BinaryReader(stream);

// Act
reader.SkipZeroes();

// Assert
stream.Position.Should().Be(3);
reader.ReadByte().Should().Be(1);
}

[Fact]
public void SkipZeroes_WithMaxLength_Test()
{
// Arrange
var data = new byte[] { 0, 0, 0, 0, 1, 2, 3 };
using var stream = new MemoryStream(data);
using var reader = new BinaryReader(stream);

// Act
reader.SkipZeroes(maxSkipLength: 2);

// Assert
stream.Position.Should().Be(2);
reader.ReadByte().Should().Be(0);
}

[Fact]
public void SkipZeroes_AllZeroes_Test()
{
// Arrange
var data = new byte[] { 0, 0, 0 };
using var stream = new MemoryStream(data);
using var reader = new BinaryReader(stream);

// Act
reader.SkipZeroes();

// Assert
stream.Position.Should().Be(3);
}

[Fact]
public void ReadNullTerminatedString_Test()
{
// Arrange
var data = Encoding.ASCII.GetBytes("hello\0");
using var stream = new MemoryStream(data);
using var reader = new BinaryReader(stream, Encoding.ASCII);

// Act
var result = reader.ReadNullTerminatedString();

// Assert
result.Should().Be("hello");
}

[Fact]
public void ReadNullTerminatedString_Empty_Test()
{
// Arrange
var data = new byte[] { 0 };
using var stream = new MemoryStream(data);
using var reader = new BinaryReader(stream, Encoding.ASCII);

// Act
var result = reader.ReadNullTerminatedString();

// Assert
result.Should().Be("");
}
}

file class NonSeekableStream(byte[] data) : MemoryStream(data)
{
public override bool CanSeek => false;
}
38 changes: 38 additions & 0 deletions PowerKit.Tests/BinaryWriterExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System.IO;
using System.Text;
using FluentAssertions;
using PowerKit.Extensions;
using Xunit;

namespace PowerKit.Tests;

public class BinaryWriterExtensionsTests
{
[Fact]
public void WriteNullTerminatedString_Test()
{
// Arrange
using var stream = new MemoryStream();
using var writer = new BinaryWriter(stream, Encoding.ASCII);

// Act
writer.WriteNullTerminatedString("hello");

// Assert
stream.ToArray().Should().Equal((byte)'h', (byte)'e', (byte)'l', (byte)'l', (byte)'o', 0);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use an u8 string here? @copilot

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in f45cd92. The assertion now uses "hello\0"u8.ToArray().

}

[Fact]
public void WriteNullTerminatedString_Empty_Test()
{
// Arrange
using var stream = new MemoryStream();
using var writer = new BinaryWriter(stream, Encoding.ASCII);

// Act
writer.WriteNullTerminatedString("");

// Assert
stream.ToArray().Should().Equal(0);
}
}
57 changes: 57 additions & 0 deletions PowerKit/Extensions/BinaryReaderExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System;
using System.IO;
using System.Text;

namespace PowerKit.Extensions;

internal static class BinaryReaderExtensions
{
extension(BinaryReader reader)
{
/// <summary>
/// Skips zero bytes in the stream, up to the specified maximum length.
/// </summary>
public void SkipZeroes(long? maxSkipLength = null)
{
if (!reader.BaseStream.CanSeek)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use Peek to avoid relying on seeking? @copilot

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 7cab462. SkipZeroes now uses PeekChar() to look ahead before consuming a byte — no seeking at all. The CanSeek guard, BaseStream.Length, and Seek(-1, Current) are all gone. The bounded variant tracks a skipped counter instead of comparing stream positions. The NonSeekableStream test and helper class were also removed since they're no longer relevant.

{
throw new InvalidOperationException("The underlying stream must be seekable.");
}

var endPosition = maxSkipLength is not null
? Math.Min(reader.BaseStream.Position + maxSkipLength.Value, reader.BaseStream.Length)
: reader.BaseStream.Length;

while (reader.BaseStream.Position < endPosition)
{
if (reader.ReadByte() != 0)
{
// Go back to non-zero byte
reader.BaseStream.Seek(-1, SeekOrigin.Current);
return;
Comment thread
Tyrrrz marked this conversation as resolved.
Outdated
}
}
}

/// <summary>
/// Reads a null-terminated string from the binary reader.
/// </summary>
public string ReadNullTerminatedString()
{
var buffer = new StringBuilder();

while (true)
{
var ch = reader.ReadChar();
if (ch == '\0')
{
break;
}

buffer.Append(ch);
}

return buffer.ToString();
}
}
}
19 changes: 19 additions & 0 deletions PowerKit/Extensions/BinaryWriterExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.IO;
using System.Text;

namespace PowerKit.Extensions;

internal static class BinaryWriterExtensions
{
extension(BinaryWriter writer)
{
/// <summary>
/// Writes a null-terminated string to the binary writer.
/// </summary>
public void WriteNullTerminatedString(string value)
{
writer.Write(Encoding.UTF8.GetBytes(value));
writer.Write((byte)0);
}
Comment thread
Tyrrrz marked this conversation as resolved.
}
}