From 1d98532672a99ca2df7527261deab5027857bbd0 Mon Sep 17 00:00:00 2001 From: Matt Janda Date: Sat, 21 Feb 2026 15:01:20 +0530 Subject: [PATCH 01/11] Always push --- .github/workflows/build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b1d6ad4..1d685ac 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -40,5 +40,4 @@ jobs: run: dotnet test - name: Push - if: github.ref == 'refs/heads/main' run: dotnet nuget push "../bin/Release/*.nupkg" -k ${{ secrets.PACKAGE_REGISTRY_TOKEN }} -s https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json --skip-duplicate From c041504693ae8996f31b1f710aa2af5576a1a907 Mon Sep 17 00:00:00 2001 From: Matt Janda Date: Sat, 21 Feb 2026 15:18:48 +0530 Subject: [PATCH 02/11] Update readme and project description --- README.md | 87 +++++++++++++++++++++++++++++++++--- src/AutoByte/AutoByte.csproj | 2 +- 2 files changed, 83 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e63d8b2..7752a63 100644 --- a/README.md +++ b/README.md @@ -5,14 +5,15 @@ [![NuGet Version](https://img.shields.io/nuget/v/AutoByte?style=flat&label=Version)](https://www.nuget.org/packages/AutoByte/) [![NuGet Downloads](https://img.shields.io/nuget/dt/AutoByte.svg?style=flat&label=Downloads)](https://www.nuget.org/packages/AutoByte) +Fast .NET data structure deserializer and parser for parsing binary data formats. -Fast .NET data structure deserializer and parser. +* Automatically generate deserialization implementation from class properties using C# source generators. -* Automatically generate implementation from the class properties. +* Provides `ByteSlide` utility for safe byte-by-byte parsing with position tracking. -* Provides `ByteSlide` parser. +* Uses `BinaryPrimitives` and `ReadOnlySpan` for efficient, zero-allocation reading. -* Use `BinaryPrimitives` and `ReadOnlySpan` to read the data. +* Supports parsing of arbitrary binary protocols and file formats (ZIP headers, Master Boot Records, custom binary structures, etc.). @@ -372,7 +373,83 @@ public void Test1() -### Resources +## Installation + +Install the NuGet package: + +```bash +dotnet add package AutoByte +``` + +Or via Package Manager Console: + +```powershell +Install-Package AutoByte +``` + +## Key Features + +### Source Generators +AutoByte uses C# 10 source generators to automatically generate the `Deserialize` method implementation. Simply decorate your class with `[AutoByteStructure]` and make it `partial`, and the code generator creates the deserialization logic for you. + +### ByteSlide Utility +The `ByteSlide` ref struct provides a safe, efficient way to parse binary data: +- Tracks position automatically as you read bytes +- Prevents reading past the buffer boundary +- Uses `ReadOnlySpan` for zero-allocation performance +- Supports both little-endian and big-endian byte order +- Methods for reading primitive types, arrays, and strings + +### Attribute-Based Configuration +* `[AutoByteStructure]` - Marks a class for code generation +* `[AutoByteField]` - Configures array/byte field parsing with `Size` or `SizeFromProperty` +* `[AutoByteString]` - Configures string field parsing (UTF-8) + +## Supported Data Types + +ByteSlide supports reading: +- Primitive types: `byte`, `sbyte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong` +- Floating point: `float`, `double` +- Enums (as their underlying type) +- Strings (UTF-8) +- Byte arrays +- Nested structures (via `IByteStructure` interface) + +## API Reference + +### ByteSlide Methods +- `Slide(int size)` - Read and advance by `size` bytes +- `Skip(int size)` - Advance by `size` bytes without reading +- `GetByte()` / `GetUInt16LittleEndian()` / `GetInt32LittleEndian()` - Read primitive types +- `GetUtf8String(int length)` - Read UTF-8 string +- `GetByteArray(int length)` - Read byte array +- `GetStructure()` - Parse nested structure + +### IByteStructure Interface +Implement this interface to manually define deserialization logic: + +```csharp +public interface IByteStructure +{ + int Deserialize(ref ByteSlide slide); +} +``` + +Return value indicates bytes to skip (usually 0). + +## Project Structure + +- `AutoByte/` - Main library with attributes and ByteSlide implementation +- `AutoByte.Generators/` - Source generator for automatic code generation +- `AutoByte.Tests/` - Unit tests with examples + +## License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +Copyright (c) 2023 Matt Janda + +## Resources Byte icon was downloaded from [Flaticon](https://www.flaticon.com/free-icon/byte_5044438) diff --git a/src/AutoByte/AutoByte.csproj b/src/AutoByte/AutoByte.csproj index e6be8ba..98d91d2 100644 --- a/src/AutoByte/AutoByte.csproj +++ b/src/AutoByte/AutoByte.csproj @@ -7,7 +7,7 @@ AutoByte AutoByte AutoByte - Fast .NET data strucure deserializer and parser + Fast .NET data structure deserializer and parser LICENSE README.md autobyte byteslide data structure deserializer From e111adb07504ee24a496b9456a5a2e22db7bb4ea Mon Sep 17 00:00:00 2001 From: Matt Janda Date: Sat, 21 Feb 2026 15:24:38 +0530 Subject: [PATCH 03/11] Add design document --- DESIGN.md | 527 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 527 insertions(+) create mode 100644 DESIGN.md diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..91007d2 --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,527 @@ +# AutoByte Design Document + +## Overview + +AutoByte is a high-performance .NET library for deserializing binary data structures. It combines C# source generators with a zero-allocation parsing utility to provide compile-time code generation and runtime efficiency for binary protocol parsing. + +## Architecture + +### Layered Design + +``` +┌─────────────────────────────────────────────────────┐ +│ User Code (decorated classes) │ +├─────────────────────────────────────────────────────┤ +│ AutoByte Core Library │ +│ ┌─────────────────────────────────────────────┐ │ +│ │ IByteStructure Interface │ │ +│ │ [AutoByteStructure] Attribute │ │ +│ │ [AutoByteField] Attribute │ │ +│ │ [AutoByteString] Attribute │ │ +│ └─────────────────────────────────────────────┘ │ +├─────────────────────────────────────────────────────┤ +│ ByteSlide - Parsing Utility │ +│ ┌─────────────────────────────────────────────┐ │ +│ │ ReadOnlySpan wrapper │ │ +│ │ Position tracking │ │ +│ │ Primitive type readers │ │ +│ │ Array/String readers │ │ +│ │ Structure recursion support │ │ +│ └─────────────────────────────────────────────┘ │ +├─────────────────────────────────────────────────────┤ +│ AutoByte.Generators (Compile-Time) │ +│ ┌─────────────────────────────────────────────┐ │ +│ │ ISourceGenerator Implementation │ │ +│ │ Syntax tree analysis │ │ +│ │ Code generation logic │ │ +│ └─────────────────────────────────────────────┘ │ +├─────────────────────────────────────────────────────┤ +│ System.Buffers.Binary (BinaryPrimitives) │ +│ ReadOnlySpan (Framework) │ +└─────────────────────────────────────────────────────┘ +``` + +## Core Components + +### 1. IByteStructure Interface + +**Location:** `AutoByte/IByteStructure.cs` + +**Purpose:** Contract for deserialization implementations + +```csharp +public partial interface IByteStructure +{ + /// + /// Deserialize bytes using byte slide. + /// + /// Byte slide deserializer + /// Return the size of the structure to move the byte pointer by the size + /// of the structure. Return zero to skip moving the byte pointer. + int Deserialize(ref ByteSlide slide); +} +``` + +**Design Rationale:** +- Ref parameter for ByteSlide allows position tracking without reference types +- Return value enables flexible pointer movement (0 for manual, or size for automatic skip) +- Partial interface allows extension through partial class declarations + +### 2. ByteSlide - The Parsing Engine + +**Location:** `AutoByte/ByteSlide.cs` + +**Type:** `ref struct` (stack-allocated, cannot be boxed or stored in fields) + +**Key Design Decisions:** + +#### Zero-Allocation Performance +- Uses `ReadOnlySpan` instead of managed arrays +- Ref struct allocation on stack instead of heap +- Slicing with `[..]` syntax creates new span views without copying data + +#### Position Tracking +```csharp +private ReadOnlySpan _slide; + +public ReadOnlySpan Slide(int size) +{ + if (size > _slide.Length) + throw new ByteSlideException(...); + + var slice = _slide[..size]; // Get first 'size' bytes + _slide = _slide[size..]; // Advance pointer + return slice; +} +``` + +#### Little-Endian and Big-Endian Support +- Delegates to `System.Buffers.Binary.BinaryPrimitives` +- Both conversion methods provided: + - `GetInt32LittleEndian()` / `GetInt32BigEndian()` + - Generic versions: `GetInt32LittleEndian()` + +#### Method Optimization +- All reader methods marked with `[MethodImpl(MethodImplOptions.AggressiveInlining)]` +- JIT compiler inlines these methods for maximum performance +- Reduces method call overhead for tight loops + +### 3. Attribute System + +#### AutoByteStructure Attribute + +**Location:** `AutoByte/AutoByteStructureAttribute.cs` + +Marks a `partial class` for code generation. + +```csharp +[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)] +public class AutoByteStructureAttribute : Attribute +{ +} +``` + +**Usage Pattern:** +```csharp +[AutoByteStructure] +public partial class MyStructure +{ + public int Field1 { get; set; } +} +``` + +#### AutoByteField Attribute + +**Location:** `AutoByte/AutoByteFieldAttribute.cs` + +Configures variable-length byte array parsing. + +```csharp +public class AutoByteFieldAttribute : Attribute +{ + /// Skip number of bytes before reading the field. + public int Skip { get; set; } + + /// Size of the field or length of the array. + public int Size { get; set; } + + /// Use deserialized property value as the size of decorated field. + public string SizeFromProperty { get; set; } +} +``` + +**Design Patterns:** + +1. **Fixed Size:** + ```csharp + [AutoByteField(Size = 3)] + public byte[] StartingCHS { get; set; } + ``` + +2. **Dynamic Size from Previous Property:** + ```csharp + public short FileNameLength { get; set; } + + [AutoByteField(SizeFromProperty = "FileNameLength")] + public byte[] FileName { get; set; } + ``` + +3. **Skip Bytes:** + ```csharp + [AutoByteField(Skip = 4)] + public byte[] Padding { get; set; } + ``` + +#### AutoByteString Attribute + +**Location:** `AutoByte/AutoByteStringAttribute.cs` + +Specialized version of AutoByteField for UTF-8 strings. + +Uses same configuration as AutoByteField but returns `string` instead of `byte[]`. + +### 4. ByteExtensions Helper + +**Location:** `AutoByte/ByteExtensions.cs` + +**Purpose:** Convenience method for parsing structures from spans + +```csharp +public static T GetStructure(this ReadOnlySpan span) + where T : IByteStructure, new() +{ + var structure = new T(); + var slide = new ByteSlide(span); + structure.Deserialize(ref slide); + return structure; +} +``` + +**Usage:** +```csharp +byte[] data = GetBinaryData(); +var result = data.GetStructure(); +``` + +## Source Generator Design + +### AutoByteSourceGenerator + +**Location:** `AutoByte.Generators/AutoByteSourceGenerator.cs` + +**Type:** Roslyn `ISourceGenerator` implementation + +**Execution Pipeline:** + +``` +1. Initialize + └─ Register SyntaxReceiver for syntax tree notifications + +2. Execute + ├─ Get all candidate classes decorated with [AutoByteStructure] + ├─ Build semantic model for each class + ├─ Extract properties and analyze attributes + ├─ Generate Deserialize method implementation + └─ Add to compilation as new source file +``` + +### Key Generation Phases + +#### Phase 1: Syntax Reception +```csharp +public class SyntaxReceiver : ISyntaxReceiver +{ + public List CandidateClasses { get; } + + public void OnVisitSyntaxNode(SyntaxNode node) + { + // Collect all partial classes with [AutoByteStructure] + } +} +``` + +**Purpose:** Pre-filter syntax tree to identify target classes before semantic analysis (performance optimization) + +#### Phase 2: Semantic Analysis +- Resolve class symbols using semantic model +- Extract attribute information +- Identify property types and names +- Validate structure + +#### Phase 3: Code Generation +- Build C# code as string +- Use StringBuilder for efficiency +- Generate complete Deserialize method +- Handle nested structures recursively + +### Generated Code Example + +**Input:** +```csharp +[AutoByteStructure] +public partial class ZipFileHeader +{ + public int Signature { get; set; } + public short VersionNeededToExtract { get; set; } + + [AutoByteString(SizeFromProperty = "FileNameLength")] + public string FileName { get; set; } +} +``` + +**Generated Output:** +```csharp +public partial class ZipFileHeader : IByteStructure +{ + public int Deserialize(ref ByteSlide slide) + { + Signature = slide.GetInt32LittleEndian(); + VersionNeededToExtract = slide.GetInt16LittleEndian(); + FileName = slide.GetUtf8String(FileNameLength); + return 0; + } +} +``` + +### Generation Strategy + +**Type Mapping:** +| C# Type | ByteSlide Method | +|---------|------------------| +| `byte` | `GetByte()` | +| `short` | `GetInt16LittleEndian()` | +| `int` | `GetInt32LittleEndian()` | +| `long` | `GetInt64LittleEndian()` | +| `ushort` | `GetUInt16LittleEndian()` | +| `uint` | `GetUInt32LittleEndian()` | +| `ulong` | `GetUInt64LittleEndian()` | +| `byte[]` | `Slide().ToArray()` (with size) | +| `string` | `GetUtf8String()` (with size) | +| `T : IByteStructure` | `GetStructure()` | + +## Design Patterns Used + +### 1. Source Generator Pattern +Uses Roslyn APIs to generate code at compile-time, eliminating runtime reflection and improving performance. + +**Benefit:** Zero-cost abstractions, type safety verified at compile-time + +### 2. Ref Struct Pattern +`ByteSlide` uses ref struct to remain stack-allocated, preventing accidental heap allocations. + +**Benefit:** Predictable memory layout, no GC pressure for tight parsing loops + +### 3. Extension Method Pattern +`ByteExtensions.GetStructure()` provides convenience API on `ReadOnlySpan`. + +**Benefit:** Fluent API, discoverable through IntelliSense + +### 4. Attribute-Driven Configuration +Properties can be configured declaratively via attributes. + +**Benefit:** Declarative, separates concern from implementation + +### 5. Partial Class Extension +Generated code extends user-written partial classes. + +**Benefit:** Clean separation, generated code invisible in editor, but still modifiable + +## Data Flow + +### Deserialization Flow + +``` +Binary Data (byte[]) + ↓ +ByteSlide (ref struct, ReadOnlySpan) + ├─ Slide(4) → reads 4 bytes, advances position + ├─ GetInt32LittleEndian() → Slide(4) + BinaryPrimitives conversion + ├─ GetUtf8String(len) → Slide(len) + UTF-8 decode + └─ GetStructure() → Deserialize(ref this) + ↓ +Generated Deserialize Method (IByteStructure) + ├─ Signature = slide.GetInt32LittleEndian() + ├─ Version = slide.GetInt16LittleEndian() + ├─ FileName = slide.GetUtf8String(FileNameLength) + └─ return 0 + ↓ +User Object (T : IByteStructure) + (all properties populated from binary data) +``` + +### Example: ZIP Header Parsing + +``` +Input: byte[] { 0x50, 0x4B, 0x03, 0x04, ... } + └─ ZIP signature: 0x04034B50 (little-endian) + +↓ + +var slide = new ByteSlide(data); +var header = slide.GetStructure(); + +↓ + +Generated Code: + Signature = slide.GetInt32LittleEndian(); // reads bytes [0:4] + Version = slide.GetInt16LittleEndian(); // reads bytes [4:6] + // ... more properties ... + +↓ + +Output: ZipFileHeader +{ + Signature = 0x04034B50, + Version = 20, + // ... properties populated ... +} +``` + +## Exception Handling + +### ByteSlideException + +**Location:** `AutoByte/ByteSlideException.cs` + +Thrown when attempting to read beyond available bytes. + +```csharp +public class ByteSlideException : Exception +{ + // Custom exception for buffer overrun detection +} +``` + +**Example:** +```csharp +var slide = new ByteSlide(new byte[10]); +slide.GetInt32LittleEndian(); // OK, 4 bytes available +slide.GetInt32LittleEndian(); // OK, 4 bytes available +slide.GetInt32LittleEndian(); // Throws: only 2 bytes left +``` + +## Performance Characteristics + +### Time Complexity +- **Deserialization:** O(n) where n = total bytes to read +- **Attribute Resolution:** O(p) during generation, where p = properties +- **Code Generation:** O(p) where p = properties + +### Space Complexity +- **ByteSlide:** O(1) - only stores span reference and length +- **Generated Code:** O(p) - one method with p statements +- **Runtime Memory:** O(n) for object properties, no intermediate allocations + +### Optimization Techniques + +1. **Method Inlining:** All ByteSlide readers marked `[MethodImpl(MethodImplOptions.AggressiveInlining)]` + +2. **Stack Allocation:** ByteSlide is ref struct, never heap-allocated + +3. **Zero-Copy Slicing:** `ReadOnlySpan[..]` creates views without copying + +4. **Compile-Time Code Generation:** No reflection or dynamic invocation at runtime + +## Extension Points + +### Custom Deserialization + +Users can implement `IByteStructure` manually for complex logic: + +```csharp +public class ComplexStructure : IByteStructure +{ + public byte[] BootCode { get; set; } + public PartitionEntry[] PartitionEntries { get; set; } + + public int Deserialize(ref ByteSlide slide) + { + // Manual parsing with custom logic + BootCode = slide.GetByteArray(446); + + PartitionEntries = new PartitionEntry[4]; + for (int i = 0; i < 4; i++) + PartitionEntries[i] = slide.GetStructure(); + + return 0; + } +} +``` + +### Endianness Handling + +ByteSlide provides both: +- `GetInt32LittleEndian()` +- `GetInt32BigEndian()` + +Choose appropriate method for your binary format. + +### Nested Structures + +Structures can contain other structures: + +```csharp +[AutoByteStructure] +public partial class Outer +{ + [AutoByteStructure] + public partial class Inner + { + public int Value { get; set; } + } + + public Inner Child { get; set; } // Generated: GetStructure() +} +``` + +## Compatibility + +- **Target Frameworks:** .NET Standard 2.0, 2.1 +- **C# Version:** 10 (for source generators) +- **Dependencies:** + - `System.Buffers.Binary` (included in .NET Standard 2.0+) + - Microsoft.CodeAnalysis.CSharp (only for generators) + +## Testing Strategy + +### Unit Tests in AutoByte.Tests + +**Test Categories:** + +1. **Code Generation Tests (`CodeGenerator_Must.cs`)** + - Verify generated code produces correct deserialization + - Example: ZIP header, Master Boot Record + +2. **ByteSlide Tests (`ByteSlide_Must.cs`)** + - Boundary conditions (reading at end of buffer) + - Byte order conversions + - String parsing + +3. **Structure Tests (`ByteStructure_Must.cs`)** + - End-to-end deserialization + - Nested structures + - Dynamic sizing with `SizeFromProperty` + +4. **Real-World Format Tests** + - Structures in `Structures/` folder (ZipFileHeader, MasterBootRecord, etc.) + - Validates against actual binary file formats + +## Future Enhancement Opportunities + +1. **Big-Endian Code Generation:** Generate big-endian readers automatically +2. **Validation Attributes:** Add validation rules (min/max, enum checks) +3. **Offset Tracking:** Return byte offset information from Deserialize +4. **Streaming Support:** Parse from Stream instead of just byte arrays +5. **Serialization:** Generate serialization (Serialize method) to complement deserialization +6. **Compression Support:** Built-in DEFLATE/GZIP decompression +7. **Custom Type Converters:** Allow user-defined type mapping + +## Summary + +AutoByte combines three powerful concepts: + +1. **C# Source Generators** for compile-time code generation +2. **Ref Structs** for zero-allocation parsing +3. **Binary Primitives** for efficient endianness-aware reading + +The result is a type-safe, high-performance binary parsing library with zero runtime overhead and maximum developer ergonomics. From 68f51ca7859e6a11318dcef03465a037a137523e Mon Sep 17 00:00:00 2001 From: Matt Janda Date: Sat, 21 Feb 2026 15:37:19 +0530 Subject: [PATCH 04/11] Add code analysis --- CODE_ANALYSIS.md | 557 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 557 insertions(+) create mode 100644 CODE_ANALYSIS.md diff --git a/CODE_ANALYSIS.md b/CODE_ANALYSIS.md new file mode 100644 index 0000000..b40ef71 --- /dev/null +++ b/CODE_ANALYSIS.md @@ -0,0 +1,557 @@ +# AutoByte Code Analysis Report + +## Executive Summary + +AutoByte is a well-designed, high-performance library with excellent use of C# source generators and ref structs. However, there are several code quality issues, potential bugs, and some areas that could be simplified. + +**Overall Assessment:** 7/10 - Good architecture, needs refactoring in specific areas + +--- + +## What's Good ✅ + +### 1. **Excellent Use of Ref Struct (`ByteSlide`)** +- Stack-allocated, zero-heap-allocation design is excellent for performance +- Prevents accidental boxing through ref struct constraint +- Proper use of `ReadOnlySpan` for zero-copy slicing +- **Impact:** Minimal GC pressure in tight parsing loops + +### 2. **Smart Inlining Strategy** +- All ByteSlide methods marked with `[MethodImpl(MethodImplOptions.AggressiveInlining)]` +- JIT compiler will inline these, reducing method call overhead +- **Impact:** Critical for performance in performance-sensitive parsing code + +### 3. **Source Generator Architecture** +- Uses Roslyn to generate code at compile-time +- No runtime reflection overhead +- Type-safe verification at build-time +- Clean separation of concerns (syntax reception → semantic analysis → code generation) + +### 4. **Endianness Flexibility** +- Both little-endian and big-endian readers available +- Generic versions allow enum casting +- Covers both common use cases (ZIP, most protocols use little-endian; network protocols use big-endian) + +### 5. **Attribute-Driven Configuration** +- Clean, declarative API via `[AutoByteStructure]`, `[AutoByteField]`, `[AutoByteString]` +- `SizeFromProperty` enables dynamic sizing based on previously-parsed fields +- Reduced boilerplate compared to manual implementations + +### 6. **DateTime Support** +- `GetCDateUtcLittleEndian()` / `GetHfsDateUtcBigEndian()` for real-world format support +- Shows consideration for actual use cases (file systems, timestamps) + +--- + +## What's Bad ❌ + +### 1. **CRITICAL BUG: `PeekStructure()` Corrupts ByteSlide State** 🐛 + +**Location:** [ByteSlide.cs](ByteSlide.cs#L346-L355) + +```csharp +public T PeekStructure() where T : IByteStructure, new() +{ + var structure = new T(); + structure.Deserialize(ref this); // ⚠️ MODIFIES 'this' even though it's a peek! + return structure; +} +``` + +**Problem:** +- "Peek" implies non-destructive read (doesn't advance pointer) +- This implementation advances the pointer because it passes `ref this` to `Deserialize` +- Method name contradicts behavior, causing subtle bugs + +**Recommendation:** +```csharp +public T PeekStructure() where T : IByteStructure, new() +{ + var savedSlide = this; // Save current state + var structure = new T(); + structure.Deserialize(ref this); + this = savedSlide; // Restore state + return structure; +} +``` + +--- + +### 2. **CRITICAL BUG: `GetCString()` Logic Error** 🐛 + +**Location:** [ByteSlide.cs](ByteSlide.cs#L241-L276) + +```csharp +public string GetCString(Encoding encoding, int maxLength) +{ + var zeroIndex = _slide.IndexOf((byte)0); + + if (zeroIndex != -1 && (zeroIndex - 1) <= maxLength) // ⚠️ OFF-BY-ONE! + { + // ... + } + + return encoding.GetString(Slide(maxLength)); +} +``` + +**Problem:** +- Condition `(zeroIndex - 1) <= maxLength` is incorrect +- Should be `zeroIndex < maxLength` or `zeroIndex <= maxLength - 1` +- Example: if `zeroIndex = 5` and `maxLength = 5`, condition evaluates to `4 <= 5` (true) + - But string of length 5 should NOT fit in maxLength of 5 +- Off-by-one error causes incorrect string length validation + +**Recommendation:** +```csharp +if (zeroIndex != -1 && zeroIndex < maxLength) +{ + // C string fits within maxLength +} +``` + +--- + +### 3. **CRITICAL BUG: `GetStructure()` Alignment Logic is Broken** 🐛 + +**Location:** [ByteSlide.cs](ByteSlide.cs#L298-L330) + +```csharp +public T GetStructure() where T : IByteStructure, new() +{ + int more; + var structure = new T(); + var start = _slide.Length; // ⚠️ Gets REMAINING length, not offset + + var size = structure.Deserialize(ref this); + + if (size > 0) + { + more = (start - _slide.Length) % size; // ⚠️ Broken math + + if (more > 0) + { + if (more > _slide.Length) + throw new ByteSlideException(...); + + _slide = _slide[more..]; // ⚠️ Skips wrong number of bytes + } + } + + return structure; +} +``` + +**Problem:** +- `start` stores remaining bytes before deserialization +- After deserialization, `start - _slide.Length` gives bytes READ +- Then `% size` tries to align to structure size +- **Logic is fundamentally flawed:** + - Should calculate: `bytes_read = start - _slide.Length` + - Should align to: how many bytes to skip to reach next multiple of `size` + - Current code: `more = bytes_read % size` then skips `more` bytes + - This means it skips 0 bytes if perfectly aligned, or skips remainder if not + - **Backward!** Should skip complement to reach next boundary + +**Example Bug:** +``` +start = 100 bytes remaining +structure.Deserialize reads 8 bytes +_slide.Length = 92 remaining + +bytes_read = 100 - 92 = 8 +more = 8 % 16 = 8 +Skips 8 bytes (wrong! structure was already 8 bytes, now you skip another 8!) +``` + +**Recommendation:** +```csharp +if (size > 0) +{ + var bytesRead = start - _slide.Length; + var remainder = bytesRead % size; + + if (remainder > 0) + { + int bytesToSkip = size - remainder; // Skip to next boundary + + if (bytesToSkip > _slide.Length) + throw new ByteSlideException(...); + + _slide = _slide[bytesToSkip..]; + } +} +``` + +--- + +### 4. **MAJOR: `GetByteArrayAlignTo()` Has Same Alignment Bug** 🐛 + +**Location:** [ByteSlide.cs](ByteSlide.cs#L281-L295) + +```csharp +public byte[] GetByteArrayAlignTo(int length, int align) +{ + var result = Slide(length).ToArray(); + + var alignment = length % align; // ⚠️ Same broken logic + + if (alignment > 0) + _slide = _slide[alignment..]; // ⚠️ Skips wrong direction + + return result; +} +``` + +**Problem:** Same as above - alignment logic is inverted + +**Recommendation:** +```csharp +public byte[] GetByteArrayAlignTo(int length, int align) +{ + var result = Slide(length).ToArray(); + + var remainder = length % align; + + if (remainder > 0) + { + int bytesToSkip = align - remainder; + if (bytesToSkip > _slide.Length) + throw new ByteSlideException(...); + _slide = _slide[bytesToSkip..]; + } + + return result; +} +``` + +--- + +### 5. **MAJOR: Debugger.Launch() in Production Code** 🐛 + +**Location:** [AutoByteSourceGenerator.cs](AutoByteSourceGenerator.cs#L15-L20) + +```csharp +public void Initialize(GeneratorInitializationContext context) +{ +#if DEBUG + if (!Debugger.IsAttached) + { + Debugger.Launch(); // ⚠️ Blocks compilation, waits for debugger + } +#endif + context.RegisterForSyntaxNotifications(() => new SyntaxReceiver()); +} +``` + +**Problem:** +- Even with `#if DEBUG`, this severely impacts build performance +- Calls `Debugger.Launch()` which **blocks entire build** waiting for debugger attachment +- Makes debugging the generator easier but ruins developer experience +- Makes CI/CD builds extremely slow if DEBUG builds are used + +**Recommendation:** +- Remove entirely, use Visual Studio debugger attach-to-process instead +- Or move to separate diagnostic build configuration +- If kept, add environment variable guard: + ```csharp + if (Environment.GetEnvironmentVariable("AUTOBYTE_DEBUG_GENERATOR") == "1") + { + if (!Debugger.IsAttached) + Debugger.Launch(); + } + ``` + +--- + +### 6. **MAJOR: Generic Exception Throwing in Source Generator** 🐛 + +**Location:** [AutoByteSourceGenerator.cs](AutoByteSourceGenerator.cs#L138, 191-192) + +```csharp +string method = propertyType switch +{ + // ... cases ... + _ => throw new Exception($"AutoByte code generator does not support {propertyType}."), +}; +``` + +**Problem:** +- Throws bare `Exception` instead of `InvalidOperationException` +- Compiler doesn't generate proper error messages through Roslyn +- Hard to diagnose which property failed and why +- Should use `context.ReportDiagnostic()` for proper compiler integration + +**Recommendation:** +```csharp +private void ReportUnsupportedType(GeneratorExecutionContext context, + ClassDeclarationSyntax classDeclaration, string propertyType) +{ + var diagnostic = Diagnostic.Create( + new DiagnosticDescriptor( + "AB001", + "Unsupported Type", + $"AutoByte does not support type '{propertyType}'", + "AutoByte", + DiagnosticSeverity.Error, + true), + classDeclaration.GetLocation()); + + context.ReportDiagnostic(diagnostic); +} +``` + +--- + +### 7. **MAJOR: Throwing in SyntaxReceiver** 🐛 + +**Location:** [AutoByteSourceGenerator.cs](AutoByteSourceGenerator.cs#L209) + +```csharp +public void OnVisitSyntaxNode(SyntaxNode syntaxNode) +{ + if (syntaxNode is ClassDeclarationSyntax classDeclaration) + { + if (!classDeclaration.IsPartial()) + throw new Exception("Use partial class with AutoByteStructure attribute."); + } +} +``` + +**Problem:** +- Throwing in `OnVisitSyntaxNode` kills the entire generator +- Should queue diagnostic and continue +- Better to report all errors at once than fail on first + +**Recommendation:** +```csharp +if (!classDeclaration.IsPartial()) +{ + _errors.Add((classDeclaration, "Use partial class with AutoByteStructure attribute.")); +} +else +{ + CandidateClasses.Add(classDeclaration); +} +``` + +Then report diagnostics in Execute phase with proper Roslyn diagnostics. + +--- + +### 8. **Code Duplication: 48+ Nearly-Identical Methods** 📊 + +**Location:** [ByteSlide.cs](ByteSlide.cs#L62-L186) + +```csharp +public short GetInt16LittleEndian() => ... +public T GetInt16LittleEndian() => ... +public ushort GetUInt16LittleEndian() => ... +public T GetUInt16LittleEndian() => ... +public short GetInt16BigEndian() => ... +public T GetInt16BigEndian() => ... +// ... repeated for int, long, uint, ulong, float, double ... +``` + +**Impact:** +- 400+ lines of nearly identical code +- Difficult to maintain +- Harder to spot bugs (and bugs exist in this section!) +- Makes API surface bloated + +**Could be eliminated with helper methods:** +```csharp +private T ReadPrimitive(Func, T> reader, int size) +{ + return reader(Slide(size)); +} + +public short GetInt16LittleEndian() + => ReadPrimitive(BinaryPrimitives.ReadInt16LittleEndian, sizeof(short)); +``` + +--- + +### 9. **Unsafe Type Casting in Generic Methods** + +**Location:** [ByteSlide.cs](ByteSlide.cs#L68, 73, etc.) + +```csharp +public T GetByte() => (T)(object)Slide(sizeof(byte))[0]; +public T GetInt16LittleEndian() => (T)(object)BinaryPrimitives.ReadInt16LittleEndian(Slide(sizeof(short))); +``` + +**Problem:** +- `(T)(object)value` is unsafe for generic types +- If T is `string` or `object`, runtime throws `InvalidCastException` +- No type safety checking at compile-time +- Generates bloated IL code + +**Better approach:** +```csharp +public T GetInt16(Func, T> converter) + => converter(Slide(sizeof(short))); + +// Usage: +GetInt16(BinaryPrimitives.ReadInt16LittleEndian) +``` + +Or use constraints: +```csharp +public T GetByte() where T : struct +{ + var value = Slide(sizeof(byte))[0]; + return (T)(object)value; // At least T is struct +} +``` + +--- + +## What's Overengineered 🏗️ + +### 1. **DateTime Helper Methods Might Be Premature** + +**Location:** [ByteSlide.cs](ByteSlide.cs#L195-L236) + +- `GetCDateUtcLittleEndian()`, `GetHfsDateUtcBigEndian()`, etc. +- These are very specific (Mac HFS, Classic Mac OS timestamps) +- Only 2-3 real-world use cases +- Adds complexity to API surface + +**Recommendation:** Move to extension methods in separate package: +```csharp +// In AutoByte.DateTimeSupport package +public static DateTime GetMacHfsDateUtc(ref ByteSlide slide) + => _hfsEpoch.AddSeconds(slide.GetUInt32LittleEndian()); +``` + +Keep core library focused. + +--- + +### 2. **`AlignTo()` Method Seems Unused** + +**Location:** [ByteSlide.cs](ByteSlide.cs#L333-L343) + +```csharp +public void AlignTo(int size, int align) +{ + var alignment = size % align; + if (alignment > 0) + _slide = _slide[alignment..]; +} +``` + +**Problem:** +- Not used in tests +- Alignment logic is wrong anyway (same as #3 above) +- Unclear use case + +**Recommendation:** Remove or clarify with real-world examples in tests. + +--- + +### 3. **Generic Overloads for Every Type** + +**Location:** [ByteSlide.cs](ByteSlide.cs#L68, 73, 79, etc.) + +```csharp +public short GetInt16LittleEndian() => ... +public T GetInt16LittleEndian() => ... // When would you use this? +``` + +**Problem:** +- Who needs to parse `int` as `ulong`? +- Generic version adds 50% more methods without clear benefit +- Just use explicit methods or extension methods + +**Recommendation:** Keep only concrete types, remove generic versions (or move to `unsafe` extension methods that require opt-in). + +--- + +### 4. **Multiple String Encodings via Attributes** + +**Location:** [AutoByteStringAttribute.cs](AutoByteStringAttribute.cs) + +```csharp +public string Encoding { get; set; } // UTF8, ASCII, Unicode, BigEndianUnicode... +public int CodePage { get; set; } // For System.Text.Encoding.CodePages +``` + +**Problem:** +- Most code uses UTF-8 +- Support for 7+ encodings adds complexity to generator +- CodePage requires extra package dependency + +**Recommendation:** Keep UTF-8 as default, provide clean extension point for custom encodings: +```csharp +[AutoByteString(Size = 32, Encoding = "custom:GetCustomEncoding()")] +``` + +--- + +## Obvious Bugs Summary 🐛 + +| Bug | Severity | Location | Impact | +|-----|----------|----------|--------| +| `PeekStructure()` advances pointer | CRITICAL | ByteSlide.cs:346 | Silent data corruption | +| `GetCString()` off-by-one check | CRITICAL | ByteSlide.cs:266 | Wrong string length validation | +| `GetStructure()` alignment broken | CRITICAL | ByteSlide.cs:310 | Incorrect alignment, data misalignment | +| `GetByteArrayAlignTo()` alignment | MAJOR | ByteSlide.cs:288 | Same alignment bug | +| `Debugger.Launch()` in generator | MAJOR | AutoByteSourceGenerator.cs:16 | Blocks builds | +| Generic exceptions in generator | MAJOR | AutoByteSourceGenerator.cs:138 | Poor error reporting | +| Exceptions in SyntaxReceiver | MAJOR | AutoByteSourceGenerator.cs:209 | Stops after first error | +| Unsafe generic casting | MEDIUM | ByteSlide.cs:68+ | Runtime errors if misused | + +--- + +## Recommendations by Priority + +### 🔴 P0: Critical Fixes Needed + +1. **Fix `PeekStructure()`** - Silent data corruption is unacceptable +2. **Fix `GetStructure()` alignment** - Alignment bugs cause data misinterpretation +3. **Fix `GetCString()` off-by-one** - String parsing is broken +4. **Remove `Debugger.Launch()`** - Breaks build productivity + +### 🟠 P1: Should Fix Soon + +5. **Replace generic exceptions with Roslyn diagnostics** - Better error UX +6. **Move error reporting to Execute() phase** - Collect all errors before failing +7. **Fix `GetByteArrayAlignTo()` alignment** - Same root cause as #2 + +### 🟡 P2: Nice to Have + +8. **Consolidate duplicate methods** - Reduce code duplication via helpers +9. **Remove generic overloads** - Not clear they're needed +10. **Move DateTime helpers to extension library** - Keep core focused +11. **Review encoding support** - Simplify to UTF-8 + extension point + +### 🔵 P3: Polish + +12. Add comprehensive tests for all edge cases +13. Document alignment semantics (or remove if not used) +14. Add XML docs to public API +15. Consider source link for debugging generated code + +--- + +## Code Quality Metrics + +| Metric | Score | Notes | +|--------|-------|-------| +| Architecture | 8/10 | Excellent source generator + ref struct design | +| API Design | 6/10 | Good core, cluttered with generics/encodings | +| Correctness | 3/10 | Multiple critical bugs in alignment and peek logic | +| Maintainability | 5/10 | High code duplication (400+ lines duplicated) | +| Performance | 9/10 | Excellent use of inlining and ref structs | +| Error Handling | 3/10 | Bare exceptions, poor diagnostics | +| Test Coverage | 4/10 | Basic tests exist, but gaps in edge cases | + +--- + +## Conclusion + +AutoByte has solid fundamentals with excellent architectural decisions around ref structs and source generators. However, **critical bugs in alignment logic and the peek method must be fixed immediately** before production use. The codebase would benefit from consolidating duplicated methods and improving error reporting through proper Roslyn diagnostics. + +**Priority: Fix critical bugs first, then refactor duplication, then optimize API surface.** From db189e6b812d3ecfd9530e8488595229ab539734 Mon Sep 17 00:00:00 2001 From: Matt Janda Date: Sat, 21 Feb 2026 16:00:11 +0530 Subject: [PATCH 05/11] Update code analysis --- CODE_ANALYSIS.md | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/CODE_ANALYSIS.md b/CODE_ANALYSIS.md index b40ef71..9e51422 100644 --- a/CODE_ANALYSIS.md +++ b/CODE_ANALYSIS.md @@ -409,27 +409,7 @@ public T GetByte() where T : struct ## What's Overengineered 🏗️ -### 1. **DateTime Helper Methods Might Be Premature** - -**Location:** [ByteSlide.cs](ByteSlide.cs#L195-L236) - -- `GetCDateUtcLittleEndian()`, `GetHfsDateUtcBigEndian()`, etc. -- These are very specific (Mac HFS, Classic Mac OS timestamps) -- Only 2-3 real-world use cases -- Adds complexity to API surface - -**Recommendation:** Move to extension methods in separate package: -```csharp -// In AutoByte.DateTimeSupport package -public static DateTime GetMacHfsDateUtc(ref ByteSlide slide) - => _hfsEpoch.AddSeconds(slide.GetUInt32LittleEndian()); -``` - -Keep core library focused. - ---- - -### 2. **`AlignTo()` Method Seems Unused** +### 1. **`AlignTo()` Method Seems Unused** **Location:** [ByteSlide.cs](ByteSlide.cs#L333-L343) @@ -451,7 +431,7 @@ public void AlignTo(int size, int align) --- -### 3. **Generic Overloads for Every Type** +### 2. **Generic Overloads for Every Type** **Location:** [ByteSlide.cs](ByteSlide.cs#L68, 73, 79, etc.) @@ -469,7 +449,7 @@ public T GetInt16LittleEndian() => ... // When would you use this? --- -### 4. **Multiple String Encodings via Attributes** +### 3. **Multiple String Encodings via Attributes** **Location:** [AutoByteStringAttribute.cs](AutoByteStringAttribute.cs) From b259234dc61784a9af1986d92bbade68629ca262 Mon Sep 17 00:00:00 2001 From: Matt Janda Date: Sat, 21 Feb 2026 16:12:14 +0530 Subject: [PATCH 06/11] Update code analysis --- CODE_ANALYSIS.md | 82 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 74 insertions(+), 8 deletions(-) diff --git a/CODE_ANALYSIS.md b/CODE_ANALYSIS.md index 9e51422..dbdf81f 100644 --- a/CODE_ANALYSIS.md +++ b/CODE_ANALYSIS.md @@ -58,23 +58,89 @@ public T PeekStructure() where T : IByteStructure, new() } ``` -**Problem:** -- "Peek" implies non-destructive read (doesn't advance pointer) -- This implementation advances the pointer because it passes `ref this` to `Deserialize` -- Method name contradicts behavior, causing subtle bugs +**The Root Cause: How `ref` Works with Structs** + +In C#, structs are value types. When passed normally, they're copied: +```csharp +public void ModifyStructCopy(ByteSlide slide) +{ + slide._slide = slide._slide[4..]; // Modifies COPY only, original unchanged +} +``` + +But with `ref`, the original is modified: +```csharp +public void ModifyStructRef(ref ByteSlide slide) +{ + slide._slide = slide._slide[4..]; // Modifies ORIGINAL via reference +} +``` + +The `PeekStructure` bug passes `ref this` to `Deserialize()`, which then modifies the **original** ByteSlide's internal state. + +**Real-World Impact Example** + +Developer's code: +```csharp +var slide = new ByteSlide(data); + +// Look ahead without consuming bytes +var peeked = slide.PeekStructure(); +Console.WriteLine($"Signature: {peeked.Signature}"); + +// Now read the actual structure (should read same bytes) +var actual = slide.GetStructure(); +Console.WriteLine($"Actual Signature: {actual.Signature}"); + +// Developer expects these to be identical +Assert.Equal(peeked.Signature, actual.Signature); // ⚠️ FAILS! +``` + +What happens: +1. `PeekStructure()` calls `Deserialize(ref this)` +2. Inside Deserialize: `slide.GetInt32LittleEndian()` advances the pointer by 4 bytes +3. `PeekStructure()` returns, but **the original ByteSlide pointer has been advanced** +4. `GetStructure()` now reads from DIFFERENT bytes (4 bytes ahead) +5. Signature values don't match → **silent data corruption** + +**Silent Nature of Bug** + +No exceptions are thrown. The developer might see: +- ZIP headers parsing fine initially +- Suddenly getting garbage data from file entries +- Spending hours debugging "parsing logic" when it's really method behavior +- Testing might pass if peek/get are never used together on same data + +**Violates Standard Semantics** + +All standard data structures define peek as non-destructive: +```csharp +var stack = new Stack { 1, 2, 3 }; +int peeked = stack.Peek(); // Returns 3, stack unchanged +int popped = stack.Pop(); // Returns 3, same value! + +var queue = new Queue { 1, 2, 3 }; +int peeked = queue.Peek(); // Returns 1, queue unchanged +int dequeued = queue.Dequeue(); // Returns 1, same value! +``` **Recommendation:** ```csharp public T PeekStructure() where T : IByteStructure, new() { - var savedSlide = this; // Save current state + var savedSlide = this; // 1. Save original state + var structure = new T(); - structure.Deserialize(ref this); - this = savedSlide; // Restore state - return structure; + structure.Deserialize(ref this); // 2. Deserialize (modifies this._slide) + + this = savedSlide; // 3. Restore original state + + return structure; // 4. Return structure, pointer unchanged } ``` +Now both calls read from the same position and return identical values. + --- ### 2. **CRITICAL BUG: `GetCString()` Logic Error** 🐛 From 4e62f269fd088d4819a23c13b12dfac40d9a4431 Mon Sep 17 00:00:00 2001 From: Matt Janda Date: Sun, 22 Feb 2026 10:37:55 +0530 Subject: [PATCH 07/11] Upgrade to .NET10 and add unit test to prove the peek structure bug --- .github/workflows/build.yml | 2 +- .github/workflows/nuget.yml | 2 +- src/AutoByte.Tests/AutoByte.Tests.csproj | 10 +-- src/AutoByte.Tests/ByteSlide_Must.cs | 77 ++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1d685ac..d4429ae 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,7 +30,7 @@ jobs: - name: Setup uses: actions/setup-dotnet@v1 with: - dotnet-version: 7.0.x + dotnet-version: 10.0.x source-url: https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json - name: Pack diff --git a/.github/workflows/nuget.yml b/.github/workflows/nuget.yml index f4a491f..1933cd4 100644 --- a/.github/workflows/nuget.yml +++ b/.github/workflows/nuget.yml @@ -27,7 +27,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v1 with: - dotnet-version: 7.0.x + dotnet-version: 10.0.x source-url: https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json - name: Pack diff --git a/src/AutoByte.Tests/AutoByte.Tests.csproj b/src/AutoByte.Tests/AutoByte.Tests.csproj index 9f0d529..27a3b38 100644 --- a/src/AutoByte.Tests/AutoByte.Tests.csproj +++ b/src/AutoByte.Tests/AutoByte.Tests.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 enable false true @@ -9,13 +9,13 @@ - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/AutoByte.Tests/ByteSlide_Must.cs b/src/AutoByte.Tests/ByteSlide_Must.cs index ec11161..c7a92b2 100644 --- a/src/AutoByte.Tests/ByteSlide_Must.cs +++ b/src/AutoByte.Tests/ByteSlide_Must.cs @@ -1,5 +1,6 @@ using System.Buffers.Binary; using System.Text; +using AutoByte.Tests.Structures; namespace AutoByte.Tests { @@ -140,6 +141,82 @@ public void GetPascalString_ReturnsExcpectedStrings() } + /// + /// Test that demonstrates the PeekStructure bug. + /// + /// BUG: PeekStructure is supposed to be non-destructive (like Stack.Peek or Queue.Peek), + /// but it advances the internal pointer due to passing `ref this` to Deserialize(). + /// + /// This test WILL FAIL with the current buggy implementation because: + /// 1. PeekStructure calls Deserialize(ref this), which advances the pointer + /// 2. GetStructure then reads from the ADVANCED position, not the original + /// 3. The signatures will differ (peeked reads correct bytes, actual reads garbage) + /// + /// The test demonstrates "silent data corruption" - no exception is thrown, + /// the methods just return different results from the same data position. + /// + [Fact] + public void PeekStructure_ShouldNotAdvancePointer_BUG() + { + // ZIP header data: 0x50 0x4B 0x03 0x04 (signature PK\x03\x04) + var data = new byte[] + { + 0x50, 0x4B, 0x03, 0x04, 0x14, 0x00, 0x00, 0x00, 0x08, 0x00, 0x63, 0x54, + 0x96, 0x56, 0x45, 0x7F, 0x6A, 0xBD, 0x5B, 0x02, 0x00, 0x00, 0xF4, 0x08, + 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x41, 0x75, 0x74, 0x6F, 0x42, 0x79, + 0x74, 0x65, 0x2E, 0x73, 0x6C, 0x6E + }; + + var slide = new ByteSlide(data); + + // Peek at the structure (should NOT advance pointer) + var peeked = slide.PeekStructure(); + + // Now get the actual structure (should read from SAME position as peek) + var actual = slide.GetStructure(); + + // These should be IDENTICAL because peek should not advance the pointer + // If they differ, it proves the bug: peek advanced the pointer + Assert.Equal(peeked.Signature, actual.Signature); + Assert.Equal(peeked.VersionNeededToExtract, actual.VersionNeededToExtract); + Assert.Equal(peeked.GeneralPurposeBitFlag, actual.GeneralPurposeBitFlag); + Assert.Equal(peeked.CompressionMethod, actual.CompressionMethod); + Assert.Equal(peeked.LastModifiedFileTime, actual.LastModifiedFileTime); + Assert.Equal(peeked.LastModifiedFileDate, actual.LastModifiedFileDate); + Assert.Equal(peeked.Crc32, actual.Crc32); + Assert.Equal(peeked.CompressedSize, actual.CompressedSize); + Assert.Equal(peeked.UncompressedSize, actual.UncompressedSize); + Assert.Equal(peeked.FileNameLength, actual.FileNameLength); + Assert.Equal(peeked.ExtraFieldLength, actual.ExtraFieldLength); + Assert.Equal(peeked.FileName, actual.FileName); + } + + /// + /// Test demonstrating that PeekString (non-generic method) works correctly. + /// This shows the pattern that Peek methods SHOULD follow: non-destructive reads. + /// + [Fact] + public void PeekString_DoesNotAdvancePointer() + { + byte[] data = { + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x57, 0x6F, 0x72, 0x6C, 0x64, 0x00 + }; + + var slide = new ByteSlide(data); + + // Peek at the string + string peeked = slide.PeekString(Encoding.ASCII, 5); + Assert.Equal("Hello", peeked); + + // Read the string (should get same data) + string read = slide.GetString(Encoding.ASCII, 5); + Assert.Equal("Hello", read); + + // If pointer wasn't advanced by peek, the next 6 bytes should be " World" + string next = slide.GetString(Encoding.ASCII, 6); + Assert.Equal(" World", next); + } + From e3159a97d6930f3d2d187948cfca9842fe7c64ae Mon Sep 17 00:00:00 2001 From: Matt Janda Date: Sun, 22 Feb 2026 10:39:55 +0530 Subject: [PATCH 08/11] Set the test project to use .NET10 --- src/AutoByte.Tests/AutoByte.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AutoByte.Tests/AutoByte.Tests.csproj b/src/AutoByte.Tests/AutoByte.Tests.csproj index 27a3b38..320cc71 100644 --- a/src/AutoByte.Tests/AutoByte.Tests.csproj +++ b/src/AutoByte.Tests/AutoByte.Tests.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 enable false true From bf3931ce07ac23042423b81a6a6b9f1fa1e233ab Mon Sep 17 00:00:00 2001 From: Matt Janda Date: Sun, 22 Feb 2026 10:47:35 +0530 Subject: [PATCH 09/11] Fix the peek structure bug --- src/AutoByte/ByteSlide.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/AutoByte/ByteSlide.cs b/src/AutoByte/ByteSlide.cs index bb82b32..c9ecdf4 100644 --- a/src/AutoByte/ByteSlide.cs +++ b/src/AutoByte/ByteSlide.cs @@ -353,8 +353,13 @@ public void AlignTo(int size, int align) [MethodImpl(MethodImplOptions.AggressiveInlining)] public T PeekStructure() where T : IByteStructure, new() { + var slide = this; // Save the original state + var structure = new T(); - structure.Deserialize(ref this); + structure.Deserialize(ref this); // Deserialize (may modify this._slide) + + this = slide; // Restore the original state + return structure; } } From a3166660000a2a0d3f4f43faee1da0521b139909 Mon Sep 17 00:00:00 2001 From: Matt Janda Date: Fri, 6 Mar 2026 12:52:42 +0530 Subject: [PATCH 10/11] Upgrade to gitversion 6 --- src/AutoByte/AutoByte.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AutoByte/AutoByte.csproj b/src/AutoByte/AutoByte.csproj index 98d91d2..331020c 100644 --- a/src/AutoByte/AutoByte.csproj +++ b/src/AutoByte/AutoByte.csproj @@ -26,7 +26,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 87a0180d72dd7892ddaec69b0bb7f0d2262672cc Mon Sep 17 00:00:00 2001 From: Matt Janda Date: Fri, 6 Mar 2026 14:37:59 +0530 Subject: [PATCH 11/11] Use gitversion 6.x for GM --- .config/dotnet-tools.json | 13 +++++++++++++ GitVersion.yml | 14 +++++++++----- 2 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 .config/dotnet-tools.json diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 0000000..821957a --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "gitversion.tool": { + "version": "6.6.0", + "commands": [ + "dotnet-gitversion" + ], + "rollForward": false + } + } +} \ No newline at end of file diff --git a/GitVersion.yml b/GitVersion.yml index b6d3892..dbe0921 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1,9 +1,13 @@ mode: ContinuousDeployment assembly-versioning-scheme: MajorMinorPatch -assembly-file-versioning-scheme: MajorMinorPatchTag -assembly-informational-format: '{SemVer}' -continuous-delivery-fallback-tag: main +assembly-informational-format: '{MajorMinorPatch}-{EscapedBranchName}.{VersionSourceDistance}' +increment: Patch branches: - master: - regex: main + main: + regex: ^main$ + label: 'main' + + unknown: + regex: ^(?.+)$ + label: '{BranchName}' \ No newline at end of file