diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 3212949..409291d 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -10,10 +10,11 @@ concurrency:
jobs:
unit:
- name: Unit tests (${{ matrix.config }})
+ name: Unit tests (${{ matrix.project }} - ${{ matrix.config }})
runs-on: ubuntu-latest
strategy:
matrix:
+ project: [RyuSocks.Test, RyuSocks.Generator.Test]
config: [Debug, Release]
steps:
- uses: actions/checkout@v4
@@ -29,7 +30,7 @@ jobs:
uses: TSRBerry/unstable-commands@v1
with:
commands: dotnet test -c "${{ matrix.config }}"
- working-directory: RyuSocks.Test
+ working-directory: ${{ matrix.project }}
timeout-minutes: 10
retry-codes: 139
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 13a50d5..4296486 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -8,6 +8,7 @@
+
diff --git a/RyuSocks.Generator.Test/PacketGeneratorTests.cs b/RyuSocks.Generator.Test/PacketGeneratorTests.cs
new file mode 100644
index 0000000..baaa333
--- /dev/null
+++ b/RyuSocks.Generator.Test/PacketGeneratorTests.cs
@@ -0,0 +1,162 @@
+using Microsoft.CodeAnalysis.Testing;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Threading.Tasks;
+using Xunit;
+using Verify = RyuSocks.Generator.Test.SourceGeneratorVerifier;
+
+namespace RyuSocks.Generator.Test
+{
+ [ExcludeFromCodeCoverage]
+ public class PacketGeneratorTests
+ {
+ private const string DataDirectory = "data/PacketGenerator/";
+ private const int DefaultSourcesAmount = 3;
+
+ private static string GetSourceFromFile(string path) => File.ReadAllText(DataDirectory + path);
+ private static (string filename, string content) GetGeneratedSourceFromFile(string path) =>
+ (Path.GetFileName(path), File.ReadAllText(DataDirectory + path));
+
+ private static (string, string)[] GetDefaultGeneratedSources(int otherExpectedSourcesLength)
+ {
+
+ (string, string)[] generatedSources = new (string, string)[DefaultSourcesAmount + otherExpectedSourcesLength];
+
+ generatedSources[0] = GetGeneratedSourceFromFile("StringEncoding.g.cs");
+ generatedSources[1] = GetGeneratedSourceFromFile("PacketFieldAttribute.g.cs");
+ generatedSources[2] = GetGeneratedSourceFromFile("Packet.g.cs");
+
+ return generatedSources;
+ }
+
+ [Theory]
+ [InlineData("SimpleProps", "TestPacket.cs",
+ "TestPacket.Byte1.g.cs", "TestPacket.SByte1.g.cs",
+ "TestPacket.UShort1.g.cs", "TestPacket.Short1.g.cs",
+ "TestPacket.UInt1.g.cs", "TestPacket.Int1.g.cs",
+ "TestPacket.ULong1.g.cs", "TestPacket.Long1.g.cs",
+ "TestPacket.String1.g.cs", "TestPacket.String2.g.cs", "TestPacket.String3.g.cs")]
+ [InlineData("SimplePropsOffsetMember", "TestPacket.cs",
+ "TestPacket.Byte1.g.cs", "TestPacket.SByte1.g.cs",
+ "TestPacket.UShort1.g.cs", "TestPacket.Short1.g.cs",
+ "TestPacket.UInt1.g.cs", "TestPacket.Int1.g.cs",
+ "TestPacket.ULong1.g.cs", "TestPacket.Long1.g.cs",
+ "TestPacket.String1.g.cs", "TestPacket.String2.g.cs", "TestPacket.String3.g.cs")]
+ [InlineData("Namespaces", "TestPacket.cs",
+ "TestSpace.TestPacket.TestField.g.cs", "TestSpace.TestPacket1.FirstField.g.cs",
+ "AnotherSpace.TestPacket.TestField.g.cs", "AnotherSpace.TestPacket2.SecondField.g.cs")]
+ [InlineData("StringEncodings", "TestPacket.cs",
+ "LittleEndian.ASCIIPacket.String1.g.cs", "LittleEndian.ASCIIPacket.String2.g.cs", "LittleEndian.ASCIIPacket.String3.g.cs", "LittleEndian.ASCIIPacket.String4.g.cs",
+ "LittleEndian.UnicodePacket.String1.g.cs", "LittleEndian.UnicodePacket.String2.g.cs", "LittleEndian.UnicodePacket.String3.g.cs", "LittleEndian.UnicodePacket.String4.g.cs",
+ "LittleEndian.UTF7Packet.String1.g.cs", "LittleEndian.UTF7Packet.String2.g.cs", "LittleEndian.UTF7Packet.String3.g.cs", "LittleEndian.UTF7Packet.String4.g.cs",
+ "LittleEndian.UTF8Packet.String1.g.cs", "LittleEndian.UTF8Packet.String2.g.cs", "LittleEndian.UTF8Packet.String3.g.cs", "LittleEndian.UTF8Packet.String4.g.cs",
+ "LittleEndian.UTF32Packet.String1.g.cs", "LittleEndian.UTF32Packet.String2.g.cs", "LittleEndian.UTF32Packet.String3.g.cs", "LittleEndian.UTF32Packet.String4.g.cs",
+ "BigEndian.ASCIIPacket.String1.g.cs", "BigEndian.ASCIIPacket.String2.g.cs", "BigEndian.ASCIIPacket.String3.g.cs", "BigEndian.ASCIIPacket.String4.g.cs",
+ "BigEndian.UnicodePacket.String1.g.cs", "BigEndian.UnicodePacket.String2.g.cs", "BigEndian.UnicodePacket.String3.g.cs", "BigEndian.UnicodePacket.String4.g.cs",
+ "BigEndian.UTF7Packet.String1.g.cs", "BigEndian.UTF7Packet.String2.g.cs", "BigEndian.UTF7Packet.String3.g.cs", "BigEndian.UTF7Packet.String4.g.cs",
+ "BigEndian.UTF8Packet.String1.g.cs", "BigEndian.UTF8Packet.String2.g.cs", "BigEndian.UTF8Packet.String3.g.cs", "BigEndian.UTF8Packet.String4.g.cs",
+ "BigEndian.UTF32Packet.String1.g.cs", "BigEndian.UTF32Packet.String2.g.cs", "BigEndian.UTF32Packet.String3.g.cs", "BigEndian.UTF32Packet.String4.g.cs")]
+ public async Task GeneratedSources_AsExpected(string directory, string sourcePath, params string[] expectedGeneratedSourcePath)
+ {
+ (string, string)[] generatedSources = GetDefaultGeneratedSources(expectedGeneratedSourcePath.Length);
+ directory += "/";
+
+ for (int i = 0; i < expectedGeneratedSourcePath.Length; i++)
+ {
+ generatedSources[DefaultSourcesAmount + i] = GetGeneratedSourceFromFile(directory + expectedGeneratedSourcePath[i]);
+ }
+
+ await Verify.VerifyGeneratedSources(
+ GetSourceFromFile(directory + sourcePath),
+ generatedSources
+ );
+ }
+
+ [Fact]
+ public async Task Generator_Fails_BadConstructorArgs()
+ {
+ const string Directory = "Exceptions/";
+ (string, string)[] generatedSources = GetDefaultGeneratedSources(0);
+ DiagnosticResult[] expectedDiagnostics = [
+ // Generator failed to generate source
+ DiagnosticResult.CompilerWarning("CS8785"),
+ // Argument 1: Cannot convert from float to int
+ DiagnosticResult.CompilerError("CS1503").WithSpan(7, 18, 7, 22).WithArguments("1", "float", "int"),
+ // Partial property must have an implementation part
+ DiagnosticResult.CompilerError("CS9248").WithSpan(8, 25, 8, 30).WithArguments("TestPacket.Byte1"),
+ ];
+
+ await Verify.VerifyGeneratedSources(
+ GetSourceFromFile(Directory + "BadConstructorArgs.cs"),
+ generatedSources,
+ expectedDiagnostics
+ );
+ }
+
+ [Fact]
+ public async Task GeneratedSources_WorksWithGeneratedTypes()
+ {
+ const int ExpectedGeneratedSourcesLength = 11;
+ const string Directory = "GeneratedTypes/";
+ (string, string)[] generatedSources = GetDefaultGeneratedSources(ExpectedGeneratedSourcesLength);
+ DiagnosticResult[] expectedDiagnostics = [
+ // Type not found
+ DiagnosticResult.CompilerError("CS0246").WithSpan(9, 20, 9, 37).WithArguments("GeneratedEnumByte"),
+ DiagnosticResult.CompilerError("CS0246").WithSpan("RyuSocks.Generator/RyuSocks.Generator.PacketGenerator/TestPacket.Byte1.g.cs", 8, 20, 8, 37).WithArguments("GeneratedEnumByte"),
+ DiagnosticResult.CompilerError("CS0246").WithSpan("RyuSocks.Generator/RyuSocks.Generator.PacketGenerator/TestPacket.Byte1.g.cs", 12, 21, 12, 38).WithArguments("GeneratedEnumByte"),
+ DiagnosticResult.CompilerError("CS0246").WithSpan(12, 22, 12, 40).WithArguments("GeneratedEnumSByte"),
+ DiagnosticResult.CompilerError("CS0246").WithSpan("RyuSocks.Generator/RyuSocks.Generator.PacketGenerator/TestPacket.SByte1.g.cs", 8, 22, 8, 40).WithArguments("GeneratedEnumSByte"),
+ DiagnosticResult.CompilerError("CS0246").WithSpan("RyuSocks.Generator/RyuSocks.Generator.PacketGenerator/TestPacket.SByte1.g.cs", 12, 21, 12, 39).WithArguments("GeneratedEnumSByte"),
+ DiagnosticResult.CompilerError("CS0246").WithSpan(15, 23, 15, 42).WithArguments("GeneratedEnumUShort"),
+ DiagnosticResult.CompilerError("CS0246").WithSpan("RyuSocks.Generator/RyuSocks.Generator.PacketGenerator/TestPacket.UShort1.g.cs", 8, 23, 8, 42).WithArguments("GeneratedEnumUShort"),
+ DiagnosticResult.CompilerError("CS0246").WithSpan("RyuSocks.Generator/RyuSocks.Generator.PacketGenerator/TestPacket.UShort1.g.cs", 12, 21, 12, 40).WithArguments("GeneratedEnumUShort"),
+ DiagnosticResult.CompilerError("CS0246").WithSpan(18, 21, 18, 39).WithArguments("GeneratedEnumShort"),
+ DiagnosticResult.CompilerError("CS0246").WithSpan("RyuSocks.Generator/RyuSocks.Generator.PacketGenerator/TestPacket.Short1.g.cs", 8, 21, 8, 39).WithArguments("GeneratedEnumShort"),
+ DiagnosticResult.CompilerError("CS0246").WithSpan("RyuSocks.Generator/RyuSocks.Generator.PacketGenerator/TestPacket.Short1.g.cs", 12, 21, 12, 39).WithArguments("GeneratedEnumShort"),
+ DiagnosticResult.CompilerError("CS0246").WithSpan(21, 31, 21, 48).WithArguments("GeneratedEnumUInt"),
+ DiagnosticResult.CompilerError("CS0246").WithSpan("RyuSocks.Generator/RyuSocks.Generator.PacketGenerator/TestPacket.UInt1.g.cs", 8, 31, 8, 48).WithArguments("GeneratedEnumUInt"),
+ DiagnosticResult.CompilerError("CS0246").WithSpan("RyuSocks.Generator/RyuSocks.Generator.PacketGenerator/TestPacket.UInt1.g.cs", 12, 21, 12, 38).WithArguments("GeneratedEnumUInt"),
+ DiagnosticResult.CompilerError("CS0246").WithSpan(24, 13, 24, 29).WithArguments("GeneratedEnumInt"),
+ DiagnosticResult.CompilerError("CS0246").WithSpan("RyuSocks.Generator/RyuSocks.Generator.PacketGenerator/TestPacket.Int1.g.cs", 8, 13, 8, 29).WithArguments("GeneratedEnumInt"),
+ DiagnosticResult.CompilerError("CS0246").WithSpan("RyuSocks.Generator/RyuSocks.Generator.PacketGenerator/TestPacket.Int1.g.cs", 12, 21, 12, 37).WithArguments("GeneratedEnumInt"),
+ DiagnosticResult.CompilerError("CS0246").WithSpan(27, 13, 27, 31).WithArguments("GeneratedEnumULong"),
+ DiagnosticResult.CompilerError("CS0246").WithSpan("RyuSocks.Generator/RyuSocks.Generator.PacketGenerator/TestPacket.ULong1.g.cs", 8, 13, 8, 31).WithArguments("GeneratedEnumULong"),
+ DiagnosticResult.CompilerError("CS0246").WithSpan("RyuSocks.Generator/RyuSocks.Generator.PacketGenerator/TestPacket.ULong1.g.cs", 12, 21, 12, 39).WithArguments("GeneratedEnumULong"),
+ DiagnosticResult.CompilerError("CS0246").WithSpan(30, 20, 30, 37).WithArguments("GeneratedEnumLong"),
+ DiagnosticResult.CompilerError("CS0246").WithSpan("RyuSocks.Generator/RyuSocks.Generator.PacketGenerator/TestPacket.Long1.g.cs", 8, 20, 8, 37).WithArguments("GeneratedEnumLong"),
+ DiagnosticResult.CompilerError("CS0246").WithSpan("RyuSocks.Generator/RyuSocks.Generator.PacketGenerator/TestPacket.Long1.g.cs", 12, 21, 12, 38).WithArguments("GeneratedEnumLong"),
+ DiagnosticResult.CompilerError("CS0246").WithSpan(33, 20, 33, 37).WithArguments("GeneratedEnumByte"),
+ DiagnosticResult.CompilerError("CS0246").WithSpan("RyuSocks.Generator/RyuSocks.Generator.PacketGenerator/TestPacket.Byte2.g.cs", 8, 20, 8, 37).WithArguments("GeneratedEnumByte"),
+ DiagnosticResult.CompilerError("CS0246").WithSpan("RyuSocks.Generator/RyuSocks.Generator.PacketGenerator/TestPacket.Byte2.g.cs", 12, 21, 12, 38).WithArguments("GeneratedEnumByte"),
+ DiagnosticResult.CompilerError("CS0246").WithSpan(36, 13, 36, 29).WithArguments("GeneratedEnumInt"),
+ DiagnosticResult.CompilerError("CS0246").WithSpan("RyuSocks.Generator/RyuSocks.Generator.PacketGenerator/TestPacket.Int2.g.cs", 8, 13, 8, 29).WithArguments("GeneratedEnumInt"),
+ DiagnosticResult.CompilerError("CS0246").WithSpan("RyuSocks.Generator/RyuSocks.Generator.PacketGenerator/TestPacket.Int2.g.cs", 12, 13, 12, 29).WithArguments("GeneratedEnumInt"),
+ DiagnosticResult.CompilerError("CS0246").WithSpan("RyuSocks.Generator/RyuSocks.Generator.PacketGenerator/TestPacket.Int2.g.cs", 12, 45, 12, 61).WithArguments("GeneratedEnumInt"),
+ DiagnosticResult.CompilerError("CS0246").WithSpan("RyuSocks.Generator/RyuSocks.Generator.PacketGenerator/TestPacket.Int2.g.cs", 16, 30, 16, 46).WithArguments("GeneratedEnumInt"),
+ DiagnosticResult.CompilerError("CS0246").WithSpan(39, 23, 39, 40).WithArguments("GeneratedEnumByte"),
+ DiagnosticResult.CompilerError("CS0246").WithSpan("RyuSocks.Generator/RyuSocks.Generator.PacketGenerator/TestPacket.Byte3.g.cs", 8, 23, 8, 40).WithArguments("GeneratedEnumByte"),
+ DiagnosticResult.CompilerError("CS0246").WithSpan("RyuSocks.Generator/RyuSocks.Generator.PacketGenerator/TestPacket.Byte3.g.cs", 12, 13, 12, 30).WithArguments("GeneratedEnumByte"),
+ DiagnosticResult.CompilerError("CS0246").WithSpan("RyuSocks.Generator/RyuSocks.Generator.PacketGenerator/TestPacket.Byte3.g.cs", 12, 46, 12, 63).WithArguments("GeneratedEnumByte"),
+ DiagnosticResult.CompilerError("CS0246").WithSpan("RyuSocks.Generator/RyuSocks.Generator.PacketGenerator/TestPacket.Byte3.g.cs", 16, 30, 16, 47).WithArguments("GeneratedEnumByte"),
+ ];
+
+ generatedSources[DefaultSourcesAmount] = GetGeneratedSourceFromFile(Directory + "TestPacket.Byte1.g.cs");
+ generatedSources[DefaultSourcesAmount + 1] = GetGeneratedSourceFromFile(Directory + "TestPacket.SByte1.g.cs");
+ generatedSources[DefaultSourcesAmount + 2] = GetGeneratedSourceFromFile(Directory + "TestPacket.UShort1.g.cs");
+ generatedSources[DefaultSourcesAmount + 3] = GetGeneratedSourceFromFile(Directory + "TestPacket.Short1.g.cs");
+ generatedSources[DefaultSourcesAmount + 4] = GetGeneratedSourceFromFile(Directory + "TestPacket.UInt1.g.cs");
+ generatedSources[DefaultSourcesAmount + 5] = GetGeneratedSourceFromFile(Directory + "TestPacket.Int1.g.cs");
+ generatedSources[DefaultSourcesAmount + 6] = GetGeneratedSourceFromFile(Directory + "TestPacket.ULong1.g.cs");
+ generatedSources[DefaultSourcesAmount + 7] = GetGeneratedSourceFromFile(Directory + "TestPacket.Long1.g.cs");
+ generatedSources[DefaultSourcesAmount + 8] = GetGeneratedSourceFromFile(Directory + "TestPacket.Byte2.g.cs");
+ generatedSources[DefaultSourcesAmount + 9] = GetGeneratedSourceFromFile(Directory + "TestPacket.Int2.g.cs");
+ generatedSources[DefaultSourcesAmount + 10] = GetGeneratedSourceFromFile(Directory + "TestPacket.Byte3.g.cs");
+
+ await Verify.VerifyGeneratedSources(
+ GetSourceFromFile(Directory + "TestPacket.cs"),
+ generatedSources,
+ expectedDiagnostics
+ );
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/RyuSocks.Generator.Test.csproj b/RyuSocks.Generator.Test/RyuSocks.Generator.Test.csproj
new file mode 100644
index 0000000..33b867f
--- /dev/null
+++ b/RyuSocks.Generator.Test/RyuSocks.Generator.Test.csproj
@@ -0,0 +1,29 @@
+
+
+ net9.0
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/RyuSocks.Generator.Test/SourceGeneratorVerifier.cs b/RyuSocks.Generator.Test/SourceGeneratorVerifier.cs
new file mode 100644
index 0000000..b5afe13
--- /dev/null
+++ b/RyuSocks.Generator.Test/SourceGeneratorVerifier.cs
@@ -0,0 +1,53 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Testing;
+using Microsoft.CodeAnalysis.Testing;
+using System;
+using System.Collections.Immutable;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading.Tasks;
+
+namespace RyuSocks.Generator.Test
+{
+ [ExcludeFromCodeCoverage]
+ public static class SourceGeneratorVerifier
+ where TSourceGenerator : new()
+ {
+ public static Task VerifyGeneratedSources(string source, (string filename, string content)[] generatedSources, DiagnosticResult[] expectedDiagnostics = null)
+ {
+ var test = new Test
+ {
+ TestCode = source
+ };
+
+ foreach ((string filename, string content) in generatedSources)
+ {
+ test.TestState.GeneratedSources.Add((typeof(TSourceGenerator), filename, content));
+ }
+
+ if (expectedDiagnostics != null)
+ {
+ test.ExpectedDiagnostics.AddRange(expectedDiagnostics);
+ }
+
+ return test.RunAsync();
+ }
+
+ public class Test : CSharpSourceGeneratorTest
+ {
+ protected override CompilationOptions CreateCompilationOptions()
+ {
+ var compilationOptions = base.CreateCompilationOptions();
+ return compilationOptions.WithSpecificDiagnosticOptions(
+ compilationOptions.SpecificDiagnosticOptions.SetItems(GetNullableWarningsFromCompiler()));
+ }
+
+ private static ImmutableDictionary GetNullableWarningsFromCompiler()
+ {
+ string[] args = ["/warnaserror:nullable"];
+ var commandLineArguments = CSharpCommandLineParser.Default.Parse(args, baseDirectory: Environment.CurrentDirectory, sdkDirectory: Environment.CurrentDirectory);
+ return commandLineArguments.CompilationOptions.SpecificDiagnosticOptions;
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/Exceptions/BadConstructorArgs.cs b/RyuSocks.Generator.Test/data/PacketGenerator/Exceptions/BadConstructorArgs.cs
new file mode 100644
index 0000000..2dc9507
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/Exceptions/BadConstructorArgs.cs
@@ -0,0 +1,9 @@
+using RyuSocks.Packets;
+
+public partial class TestPacket : Packet
+{
+ private int SecondOffset => 1;
+
+ [PacketField(1.5f)]
+ public partial byte Byte1 { get; set; }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/GeneratedTypes/TestPacket.Byte1.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/GeneratedTypes/TestPacket.Byte1.g.cs
new file mode 100644
index 0000000..8462704
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/GeneratedTypes/TestPacket.Byte1.g.cs
@@ -0,0 +1,19 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+partial class TestPacket
+{
+ public partial GeneratedEnumByte Byte1
+ {
+ get
+ {
+ return (GeneratedEnumByte)this[0];
+ }
+ set
+ {
+ this[0] = (byte)value;
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/GeneratedTypes/TestPacket.Byte2.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/GeneratedTypes/TestPacket.Byte2.g.cs
new file mode 100644
index 0000000..ddb869a
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/GeneratedTypes/TestPacket.Byte2.g.cs
@@ -0,0 +1,19 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+partial class TestPacket
+{
+ public partial GeneratedEnumByte Byte2
+ {
+ get
+ {
+ return (GeneratedEnumByte)this[this.AnOffset];
+ }
+ set
+ {
+ this[this.AnOffset] = (byte)value;
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/GeneratedTypes/TestPacket.Byte3.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/GeneratedTypes/TestPacket.Byte3.g.cs
new file mode 100644
index 0000000..090ade7
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/GeneratedTypes/TestPacket.Byte3.g.cs
@@ -0,0 +1,34 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+partial class TestPacket
+{
+ protected partial GeneratedEnumByte[] Byte3
+ {
+ get
+ {
+ GeneratedEnumByte[] result = new GeneratedEnumByte[this.ALength];
+
+ for (int i = 0; i < this.ALength; i++)
+ {
+ result[i] = (GeneratedEnumByte)this[this.AnOffset + i];
+ }
+
+ return result;
+ }
+ set
+ {
+ if (value.Length != this.ALength)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value.Length), value.Length, $"{nameof(value.Length)} must be equal to: {this.ALength}");
+ }
+
+ for (int i = 0; i < this.ALength; i++)
+ {
+ this[this.AnOffset + i] = (byte)value[i];
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/GeneratedTypes/TestPacket.Int1.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/GeneratedTypes/TestPacket.Int1.g.cs
new file mode 100644
index 0000000..bef5b7a
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/GeneratedTypes/TestPacket.Int1.g.cs
@@ -0,0 +1,19 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+partial class TestPacket
+{
+ partial GeneratedEnumInt Int1
+ {
+ get
+ {
+ return (GeneratedEnumInt)BinaryPrimitives.ReadInt32LittleEndian(this.AsSpan(5, 4));
+ }
+ set
+ {
+ BinaryPrimitives.WriteInt32LittleEndian(this.AsSpan(5, 4), (int)value);
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/GeneratedTypes/TestPacket.Int2.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/GeneratedTypes/TestPacket.Int2.g.cs
new file mode 100644
index 0000000..72766eb
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/GeneratedTypes/TestPacket.Int2.g.cs
@@ -0,0 +1,34 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+partial class TestPacket
+{
+ partial GeneratedEnumInt[] Int2
+ {
+ get
+ {
+ GeneratedEnumInt[] result = new GeneratedEnumInt[this.ALength];
+
+ for (int i = 0; i < this.ALength; i++)
+ {
+ result[i] = (GeneratedEnumInt)BinaryPrimitives.ReadInt32LittleEndian(this.AsSpan(9 + (i * 4), 4));
+ }
+
+ return result;
+ }
+ set
+ {
+ if (value.Length != this.ALength)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value.Length), value.Length, $"{nameof(value.Length)} must be equal to: {this.ALength}");
+ }
+
+ for (int i = 0; i < this.ALength; i++)
+ {
+ BinaryPrimitives.WriteInt32LittleEndian(this.AsSpan(9 + (i * 4), 4), (int)value[i]);
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/GeneratedTypes/TestPacket.Long1.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/GeneratedTypes/TestPacket.Long1.g.cs
new file mode 100644
index 0000000..664a33d
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/GeneratedTypes/TestPacket.Long1.g.cs
@@ -0,0 +1,19 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+partial class TestPacket
+{
+ public partial GeneratedEnumLong Long1
+ {
+ get
+ {
+ return (GeneratedEnumLong)BinaryPrimitives.ReadInt64LittleEndian(this.AsSpan(7, 8));
+ }
+ set
+ {
+ BinaryPrimitives.WriteInt64LittleEndian(this.AsSpan(7, 8), (long)value);
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/GeneratedTypes/TestPacket.SByte1.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/GeneratedTypes/TestPacket.SByte1.g.cs
new file mode 100644
index 0000000..1a35637
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/GeneratedTypes/TestPacket.SByte1.g.cs
@@ -0,0 +1,19 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+partial class TestPacket
+{
+ internal partial GeneratedEnumSByte SByte1
+ {
+ get
+ {
+ return (GeneratedEnumSByte)(sbyte)this[1];
+ }
+ set
+ {
+ this[1] = (byte)value;
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/GeneratedTypes/TestPacket.Short1.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/GeneratedTypes/TestPacket.Short1.g.cs
new file mode 100644
index 0000000..fc0c710
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/GeneratedTypes/TestPacket.Short1.g.cs
@@ -0,0 +1,19 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+partial class TestPacket
+{
+ private partial GeneratedEnumShort Short1
+ {
+ get
+ {
+ return (GeneratedEnumShort)BinaryPrimitives.ReadInt16LittleEndian(this.AsSpan(3, 2));
+ }
+ set
+ {
+ BinaryPrimitives.WriteInt16LittleEndian(this.AsSpan(3, 2), (short)value);
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/GeneratedTypes/TestPacket.UInt1.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/GeneratedTypes/TestPacket.UInt1.g.cs
new file mode 100644
index 0000000..aff640e
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/GeneratedTypes/TestPacket.UInt1.g.cs
@@ -0,0 +1,19 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+partial class TestPacket
+{
+ protected private partial GeneratedEnumUInt UInt1
+ {
+ get
+ {
+ return (GeneratedEnumUInt)BinaryPrimitives.ReadUInt32LittleEndian(this.AsSpan(4, 4));
+ }
+ set
+ {
+ BinaryPrimitives.WriteUInt32LittleEndian(this.AsSpan(4, 4), (uint)value);
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/GeneratedTypes/TestPacket.ULong1.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/GeneratedTypes/TestPacket.ULong1.g.cs
new file mode 100644
index 0000000..c870820
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/GeneratedTypes/TestPacket.ULong1.g.cs
@@ -0,0 +1,19 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+partial class TestPacket
+{
+ partial GeneratedEnumULong ULong1
+ {
+ get
+ {
+ return (GeneratedEnumULong)BinaryPrimitives.ReadUInt64LittleEndian(this.AsSpan(6, 8));
+ }
+ set
+ {
+ BinaryPrimitives.WriteUInt64LittleEndian(this.AsSpan(6, 8), (ulong)value);
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/GeneratedTypes/TestPacket.UShort1.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/GeneratedTypes/TestPacket.UShort1.g.cs
new file mode 100644
index 0000000..5524d45
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/GeneratedTypes/TestPacket.UShort1.g.cs
@@ -0,0 +1,19 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+partial class TestPacket
+{
+ protected partial GeneratedEnumUShort UShort1
+ {
+ get
+ {
+ return (GeneratedEnumUShort)BinaryPrimitives.ReadUInt16LittleEndian(this.AsSpan(2, 2));
+ }
+ set
+ {
+ BinaryPrimitives.WriteUInt16LittleEndian(this.AsSpan(2, 2), (ushort)value);
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/GeneratedTypes/TestPacket.cs b/RyuSocks.Generator.Test/data/PacketGenerator/GeneratedTypes/TestPacket.cs
new file mode 100644
index 0000000..b0d62ac
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/GeneratedTypes/TestPacket.cs
@@ -0,0 +1,40 @@
+using RyuSocks.Packets;
+
+public partial class TestPacket : Packet
+{
+ private int AnOffset => 2;
+ protected int ALength => 3;
+
+ [PacketField(0, AssumeGeneratedEnumType="byte")]
+ public partial GeneratedEnumByte Byte1 { get; set; }
+
+ [PacketField(1, AssumeGeneratedEnumType="SBYTE")]
+ internal partial GeneratedEnumSByte SByte1 { get; set; }
+
+ [PacketField(2, AssumeGeneratedEnumType="UsHoRt")]
+ protected partial GeneratedEnumUShort UShort1 { get; set; }
+
+ [PacketField(3, AssumeGeneratedEnumType="sHoRt")]
+ private partial GeneratedEnumShort Short1 { get; set; }
+
+ [PacketField(4, AssumeGeneratedEnumType="uint")]
+ protected private partial GeneratedEnumUInt UInt1 { get; set; }
+
+ [PacketField(5, AssumeGeneratedEnumType="int")]
+ partial GeneratedEnumInt Int1 { get; set; }
+
+ [PacketField(6, AssumeGeneratedEnumType="ulong")]
+ partial GeneratedEnumULong ULong1 { get; set; }
+
+ [PacketField(7, AssumeGeneratedEnumType="long")]
+ public partial GeneratedEnumLong Long1 { get; set; }
+
+ [PacketField(nameof(AnOffset), AssumeGeneratedEnumType="byte")]
+ public partial GeneratedEnumByte Byte2 { get; set; }
+
+ [PacketField(9, LengthMember = nameof(ALength), AssumeGeneratedEnumType="int")]
+ partial GeneratedEnumInt[] Int2 { get; set; }
+
+ [PacketField(nameof(AnOffset), LengthMember = nameof(ALength), AssumeGeneratedEnumType="byte")]
+ protected partial GeneratedEnumByte[] Byte3 { get; set; }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/Namespaces/AnotherSpace.TestPacket.TestField.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/Namespaces/AnotherSpace.TestPacket.TestField.g.cs
new file mode 100644
index 0000000..dde2b3b
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/Namespaces/AnotherSpace.TestPacket.TestField.g.cs
@@ -0,0 +1,22 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace AnotherSpace
+{
+ partial class TestPacket
+ {
+ public partial byte TestField
+ {
+ get
+ {
+ return this[4];
+ }
+ set
+ {
+ this[4] = value;
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/Namespaces/AnotherSpace.TestPacket2.SecondField.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/Namespaces/AnotherSpace.TestPacket2.SecondField.g.cs
new file mode 100644
index 0000000..4374a95
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/Namespaces/AnotherSpace.TestPacket2.SecondField.g.cs
@@ -0,0 +1,22 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace AnotherSpace
+{
+ partial class TestPacket2
+ {
+ private partial byte SecondField
+ {
+ get
+ {
+ return this[2];
+ }
+ set
+ {
+ this[2] = value;
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/Namespaces/TestPacket.cs b/RyuSocks.Generator.Test/data/PacketGenerator/Namespaces/TestPacket.cs
new file mode 100644
index 0000000..c259ded
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/Namespaces/TestPacket.cs
@@ -0,0 +1,31 @@
+using RyuSocks.Packets;
+
+namespace TestSpace
+{
+ public partial class TestPacket : Packet
+ {
+ [PacketField(0)]
+ internal partial byte TestField { get; set; }
+ }
+
+ partial class TestPacket1 : Packet
+ {
+ [PacketField(1)]
+ public partial byte FirstField { get; set; }
+ }
+}
+
+namespace AnotherSpace
+{
+ partial class TestPacket : Packet
+ {
+ [PacketField(4)]
+ public partial byte TestField { get; set; }
+ }
+
+ internal partial class TestPacket2 : Packet
+ {
+ [PacketField(2)]
+ private partial byte SecondField { get; set; }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/Namespaces/TestSpace.TestPacket.TestField.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/Namespaces/TestSpace.TestPacket.TestField.g.cs
new file mode 100644
index 0000000..df4afcc
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/Namespaces/TestSpace.TestPacket.TestField.g.cs
@@ -0,0 +1,22 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace TestSpace
+{
+ partial class TestPacket
+ {
+ internal partial byte TestField
+ {
+ get
+ {
+ return this[0];
+ }
+ set
+ {
+ this[0] = value;
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/Namespaces/TestSpace.TestPacket1.FirstField.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/Namespaces/TestSpace.TestPacket1.FirstField.g.cs
new file mode 100644
index 0000000..88fb785
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/Namespaces/TestSpace.TestPacket1.FirstField.g.cs
@@ -0,0 +1,22 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace TestSpace
+{
+ partial class TestPacket1
+ {
+ public partial byte FirstField
+ {
+ get
+ {
+ return this[1];
+ }
+ set
+ {
+ this[1] = value;
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/Packet.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/Packet.g.cs
new file mode 100644
index 0000000..58897d9
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/Packet.g.cs
@@ -0,0 +1,42 @@
+using System;
+
+namespace RyuSocks.Packets
+{
+ public abstract partial class Packet
+ {
+ ///
+ /// The contents of the packet.
+ ///
+ public byte[] Bytes { get; protected set; }
+
+ ///
+ public byte this[int i]
+ {
+ get => Bytes[i];
+ set => Bytes[i] = value;
+ }
+
+ ///
+ /// Creates a new span over the packet.
+ ///
+ ///
+ ///
+ public Span AsSpan() => Bytes;
+
+ ///
+ /// Creates a new Span over the portion of the packet beginning
+ /// at 'start' index and ending at 'end' index (exclusive).
+ ///
+ /// The index at which to begin the Span.
+ /// The number of items in the Span.
+ ///
+ public Span AsSpan(int start, int length) => Bytes.AsSpan(start, length);
+
+ protected Packet() { }
+
+ protected Packet(byte[] bytes)
+ {
+ Bytes = bytes;
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/PacketFieldAttribute.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/PacketFieldAttribute.g.cs
new file mode 100644
index 0000000..76922c3
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/PacketFieldAttribute.g.cs
@@ -0,0 +1,83 @@
+using System;
+
+namespace RyuSocks.Packets
+{
+ [AttributeUsage(AttributeTargets.Property, Inherited = false)]
+ [System.Diagnostics.Conditional("RyuSocks_PacketGenerator_DEBUG")]
+ public sealed class PacketFieldAttribute : Attribute
+ {
+ private int offset = -1;
+ private string offsetMember = string.Empty;
+
+ ///
+ /// Marks this property as a field of the packet.
+ /// The containing class needs to extend .
+ ///
+ /// The offset of this field in the packet.
+ public PacketFieldAttribute([System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] int offset)
+ {
+ this.offset = offset;
+ }
+
+ ///
+ /// Marks this property as a field of the packet.
+ /// The containing class needs to extend .
+ ///
+ /// The name of the member which specifies the offset of this field in the packet.
+ public PacketFieldAttribute(string offsetMember)
+ {
+ this.offsetMember = offsetMember;
+ }
+
+ ///
+ /// The length of this field in bytes.
+ /// Only required if it can't be determined from the property type.
+ ///
+ public int Length { get; set; } = -1;
+
+ ///
+ /// The name of the member which specifies the length of this field in bytes.
+ /// Only required if it can't be determined from the property type.
+ ///
+ public string LengthMember { get; set; } = string.Empty;
+
+ ///
+ /// Whether this field is big endian.
+ ///
+ public bool IsBigEndian { get; set; } = false;
+
+ ///
+ /// The minimum length of the array or chars allowed in this field.
+ ///
+ public int MinLength { get; set; } = -1;
+
+ ///
+ /// The maximum length of the array or chars allowed in this field.
+ ///
+ public int MaxLength { get; set; } = -1;
+
+ ///
+ /// The encoding of the underlying string.
+ /// Only required for string properties.
+ ///
+ public StringEncoding StringEncoding = StringEncoding.ASCII;
+
+ ///
+ /// The name of the underlying type of the property type.
+ /// Only required if the type of the property is source generated.
+ ///
+ public string AssumeGeneratedEnumType { get; set; } = string.Empty;
+
+ ///
+ /// The name of the method which should be invoked before the getter/setter is executed.
+ ///
+ ///
+ /// The method type must be void and have one optional parameter with the same type as the property.
+ /// If verification fails an exception should be thrown.
+ ///
+ public string ValidationMethod { get; set; } = string.Empty;
+
+ public int Offset => offset;
+ public string OffsetMember => offsetMember;
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/SimpleProps/TestPacket.Byte1.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/SimpleProps/TestPacket.Byte1.g.cs
new file mode 100644
index 0000000..e963d9e
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/SimpleProps/TestPacket.Byte1.g.cs
@@ -0,0 +1,19 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+partial class TestPacket
+{
+ public partial byte Byte1
+ {
+ get
+ {
+ return this[0];
+ }
+ set
+ {
+ this[0] = value;
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/SimpleProps/TestPacket.Int1.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/SimpleProps/TestPacket.Int1.g.cs
new file mode 100644
index 0000000..a00916a
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/SimpleProps/TestPacket.Int1.g.cs
@@ -0,0 +1,19 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+partial class TestPacket
+{
+ partial int Int1
+ {
+ get
+ {
+ return BinaryPrimitives.ReadInt32LittleEndian(this.AsSpan(5, 4));
+ }
+ set
+ {
+ BinaryPrimitives.WriteInt32LittleEndian(this.AsSpan(5, 4), value);
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/SimpleProps/TestPacket.Long1.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/SimpleProps/TestPacket.Long1.g.cs
new file mode 100644
index 0000000..8737c17
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/SimpleProps/TestPacket.Long1.g.cs
@@ -0,0 +1,19 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+partial class TestPacket
+{
+ public partial long Long1
+ {
+ get
+ {
+ return BinaryPrimitives.ReadInt64LittleEndian(this.AsSpan(7, 8));
+ }
+ set
+ {
+ BinaryPrimitives.WriteInt64LittleEndian(this.AsSpan(7, 8), value);
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/SimpleProps/TestPacket.SByte1.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/SimpleProps/TestPacket.SByte1.g.cs
new file mode 100644
index 0000000..e2bdc20
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/SimpleProps/TestPacket.SByte1.g.cs
@@ -0,0 +1,19 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+partial class TestPacket
+{
+ internal partial sbyte SByte1
+ {
+ get
+ {
+ return (sbyte)this[1];
+ }
+ set
+ {
+ this[1] = (byte)value;
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/SimpleProps/TestPacket.Short1.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/SimpleProps/TestPacket.Short1.g.cs
new file mode 100644
index 0000000..b758aef
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/SimpleProps/TestPacket.Short1.g.cs
@@ -0,0 +1,19 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+partial class TestPacket
+{
+ private partial short Short1
+ {
+ get
+ {
+ return BinaryPrimitives.ReadInt16LittleEndian(this.AsSpan(3, 2));
+ }
+ set
+ {
+ BinaryPrimitives.WriteInt16LittleEndian(this.AsSpan(3, 2), value);
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/SimpleProps/TestPacket.String1.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/SimpleProps/TestPacket.String1.g.cs
new file mode 100644
index 0000000..b6aef48
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/SimpleProps/TestPacket.String1.g.cs
@@ -0,0 +1,19 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+partial class TestPacket
+{
+ private partial string String1
+ {
+ get
+ {
+ return Encoding.ASCII.GetString(this.AsSpan(8, 10));
+ }
+ set
+ {
+ Encoding.ASCII.GetBytes(value, this.AsSpan(8, 10));
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/SimpleProps/TestPacket.String2.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/SimpleProps/TestPacket.String2.g.cs
new file mode 100644
index 0000000..2a09440
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/SimpleProps/TestPacket.String2.g.cs
@@ -0,0 +1,23 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+partial class TestPacket
+{
+ partial string String2
+ {
+ get
+ {
+ return Encoding.ASCII.GetString(this.AsSpan(9, this.AStringByteLength));
+ }
+ set
+ {
+ if (Encoding.ASCII.GetByteCount(value) != this.AStringByteLength)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), Encoding.ASCII.GetByteCount(value), $"byte length of value must be equal to: {this.AStringByteLength}");
+ }
+ Encoding.ASCII.GetBytes(value, this.AsSpan(9, this.AStringByteLength));
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/SimpleProps/TestPacket.String3.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/SimpleProps/TestPacket.String3.g.cs
new file mode 100644
index 0000000..6e998b0
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/SimpleProps/TestPacket.String3.g.cs
@@ -0,0 +1,20 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+partial class TestPacket
+{
+ protected partial string String3
+ {
+ get
+ {
+ return Encoding.ASCII.GetString(this.AsSpan(10, this.ReadWriteByteLength));
+ }
+ set
+ {
+ this.ReadWriteByteLength = Encoding.ASCII.GetByteCount(value);
+ Encoding.ASCII.GetBytes(value, this.AsSpan(10, this.ReadWriteByteLength));
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/SimpleProps/TestPacket.UInt1.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/SimpleProps/TestPacket.UInt1.g.cs
new file mode 100644
index 0000000..51cce17
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/SimpleProps/TestPacket.UInt1.g.cs
@@ -0,0 +1,19 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+partial class TestPacket
+{
+ protected private partial uint UInt1
+ {
+ get
+ {
+ return BinaryPrimitives.ReadUInt32LittleEndian(this.AsSpan(4, 4));
+ }
+ set
+ {
+ BinaryPrimitives.WriteUInt32LittleEndian(this.AsSpan(4, 4), value);
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/SimpleProps/TestPacket.ULong1.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/SimpleProps/TestPacket.ULong1.g.cs
new file mode 100644
index 0000000..6c8cfd8
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/SimpleProps/TestPacket.ULong1.g.cs
@@ -0,0 +1,19 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+partial class TestPacket
+{
+ partial ulong ULong1
+ {
+ get
+ {
+ return BinaryPrimitives.ReadUInt64LittleEndian(this.AsSpan(6, 8));
+ }
+ set
+ {
+ BinaryPrimitives.WriteUInt64LittleEndian(this.AsSpan(6, 8), value);
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/SimpleProps/TestPacket.UShort1.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/SimpleProps/TestPacket.UShort1.g.cs
new file mode 100644
index 0000000..1676984
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/SimpleProps/TestPacket.UShort1.g.cs
@@ -0,0 +1,19 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+partial class TestPacket
+{
+ protected partial ushort UShort1
+ {
+ get
+ {
+ return BinaryPrimitives.ReadUInt16LittleEndian(this.AsSpan(2, 2));
+ }
+ set
+ {
+ BinaryPrimitives.WriteUInt16LittleEndian(this.AsSpan(2, 2), value);
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/SimpleProps/TestPacket.cs b/RyuSocks.Generator.Test/data/PacketGenerator/SimpleProps/TestPacket.cs
new file mode 100644
index 0000000..f3a6c85
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/SimpleProps/TestPacket.cs
@@ -0,0 +1,45 @@
+using RyuSocks.Packets;
+
+public partial class TestPacket : Packet
+{
+ private int SecondOffset => 1;
+
+ protected int AStringByteLength => 2;
+
+ public int ReadWriteByteLength { get; set; } = 34;
+
+ public byte Byte0 { get; set; }
+
+ [PacketField(0)]
+ public partial byte Byte1 { get; set; }
+
+ [PacketField(1)]
+ internal partial sbyte SByte1 { get; set; }
+
+ [PacketField(2)]
+ protected partial ushort UShort1 { get; set; }
+
+ [PacketField(3)]
+ private partial short Short1 { get; set; }
+
+ [PacketField(4)]
+ protected private partial uint UInt1 { get; set; }
+
+ [PacketField(5)]
+ partial int Int1 { get; set; }
+
+ [PacketField(6)]
+ partial ulong ULong1 { get; set; }
+
+ [PacketField(7)]
+ public partial long Long1 { get; set; }
+
+ [PacketField(8, Length = 10)]
+ private partial string String1 { get; set; }
+
+ [PacketField(9, LengthMember = nameof(AStringByteLength))]
+ partial string String2 { get; set; }
+
+ [PacketField(10, LengthMember = nameof(ReadWriteByteLength))]
+ protected partial string String3 { get; set; }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/SimplePropsOffsetMember/TestPacket.Byte1.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/SimplePropsOffsetMember/TestPacket.Byte1.g.cs
new file mode 100644
index 0000000..ea17cab
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/SimplePropsOffsetMember/TestPacket.Byte1.g.cs
@@ -0,0 +1,19 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+partial class TestPacket
+{
+ public partial byte Byte1
+ {
+ get
+ {
+ return this[this.AnOffset];
+ }
+ set
+ {
+ this[this.AnOffset] = value;
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/SimplePropsOffsetMember/TestPacket.Int1.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/SimplePropsOffsetMember/TestPacket.Int1.g.cs
new file mode 100644
index 0000000..7a1da33
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/SimplePropsOffsetMember/TestPacket.Int1.g.cs
@@ -0,0 +1,19 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+partial class TestPacket
+{
+ partial int Int1
+ {
+ get
+ {
+ return BinaryPrimitives.ReadInt32LittleEndian(this.AsSpan(this.AnOffset, 4));
+ }
+ set
+ {
+ BinaryPrimitives.WriteInt32LittleEndian(this.AsSpan(this.AnOffset, 4), value);
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/SimplePropsOffsetMember/TestPacket.Long1.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/SimplePropsOffsetMember/TestPacket.Long1.g.cs
new file mode 100644
index 0000000..733cbc9
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/SimplePropsOffsetMember/TestPacket.Long1.g.cs
@@ -0,0 +1,19 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+partial class TestPacket
+{
+ public partial long Long1
+ {
+ get
+ {
+ return BinaryPrimitives.ReadInt64LittleEndian(this.AsSpan(this.AnOffset, 8));
+ }
+ set
+ {
+ BinaryPrimitives.WriteInt64LittleEndian(this.AsSpan(this.AnOffset, 8), value);
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/SimplePropsOffsetMember/TestPacket.SByte1.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/SimplePropsOffsetMember/TestPacket.SByte1.g.cs
new file mode 100644
index 0000000..0323082
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/SimplePropsOffsetMember/TestPacket.SByte1.g.cs
@@ -0,0 +1,19 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+partial class TestPacket
+{
+ internal partial sbyte SByte1
+ {
+ get
+ {
+ return (sbyte)this[this.AnOffset];
+ }
+ set
+ {
+ this[this.AnOffset] = (byte)value;
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/SimplePropsOffsetMember/TestPacket.Short1.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/SimplePropsOffsetMember/TestPacket.Short1.g.cs
new file mode 100644
index 0000000..385fa58
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/SimplePropsOffsetMember/TestPacket.Short1.g.cs
@@ -0,0 +1,19 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+partial class TestPacket
+{
+ private partial short Short1
+ {
+ get
+ {
+ return BinaryPrimitives.ReadInt16LittleEndian(this.AsSpan(this.AnOffset, 2));
+ }
+ set
+ {
+ BinaryPrimitives.WriteInt16LittleEndian(this.AsSpan(this.AnOffset, 2), value);
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/SimplePropsOffsetMember/TestPacket.String1.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/SimplePropsOffsetMember/TestPacket.String1.g.cs
new file mode 100644
index 0000000..e50e4ee
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/SimplePropsOffsetMember/TestPacket.String1.g.cs
@@ -0,0 +1,19 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+partial class TestPacket
+{
+ private partial string String1
+ {
+ get
+ {
+ return Encoding.ASCII.GetString(this.AsSpan(this.AnOffset, 10));
+ }
+ set
+ {
+ Encoding.ASCII.GetBytes(value, this.AsSpan(this.AnOffset, 10));
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/SimplePropsOffsetMember/TestPacket.String2.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/SimplePropsOffsetMember/TestPacket.String2.g.cs
new file mode 100644
index 0000000..6f7a4d2
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/SimplePropsOffsetMember/TestPacket.String2.g.cs
@@ -0,0 +1,23 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+partial class TestPacket
+{
+ partial string String2
+ {
+ get
+ {
+ return Encoding.ASCII.GetString(this.AsSpan(this.AnOffset, this.AStringByteLength));
+ }
+ set
+ {
+ if (Encoding.ASCII.GetByteCount(value) != this.AStringByteLength)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), Encoding.ASCII.GetByteCount(value), $"byte length of value must be equal to: {this.AStringByteLength}");
+ }
+ Encoding.ASCII.GetBytes(value, this.AsSpan(this.AnOffset, this.AStringByteLength));
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/SimplePropsOffsetMember/TestPacket.String3.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/SimplePropsOffsetMember/TestPacket.String3.g.cs
new file mode 100644
index 0000000..84fef44
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/SimplePropsOffsetMember/TestPacket.String3.g.cs
@@ -0,0 +1,20 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+partial class TestPacket
+{
+ protected partial string String3
+ {
+ get
+ {
+ return Encoding.ASCII.GetString(this.AsSpan(this.AnOffset, this.ReadWriteByteLength));
+ }
+ set
+ {
+ this.ReadWriteByteLength = Encoding.ASCII.GetByteCount(value);
+ Encoding.ASCII.GetBytes(value, this.AsSpan(this.AnOffset, this.ReadWriteByteLength));
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/SimplePropsOffsetMember/TestPacket.UInt1.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/SimplePropsOffsetMember/TestPacket.UInt1.g.cs
new file mode 100644
index 0000000..ff385ce
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/SimplePropsOffsetMember/TestPacket.UInt1.g.cs
@@ -0,0 +1,19 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+partial class TestPacket
+{
+ protected private partial uint UInt1
+ {
+ get
+ {
+ return BinaryPrimitives.ReadUInt32LittleEndian(this.AsSpan(this.AnOffset, 4));
+ }
+ set
+ {
+ BinaryPrimitives.WriteUInt32LittleEndian(this.AsSpan(this.AnOffset, 4), value);
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/SimplePropsOffsetMember/TestPacket.ULong1.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/SimplePropsOffsetMember/TestPacket.ULong1.g.cs
new file mode 100644
index 0000000..2cabde1
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/SimplePropsOffsetMember/TestPacket.ULong1.g.cs
@@ -0,0 +1,19 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+partial class TestPacket
+{
+ partial ulong ULong1
+ {
+ get
+ {
+ return BinaryPrimitives.ReadUInt64LittleEndian(this.AsSpan(this.AnOffset, 8));
+ }
+ set
+ {
+ BinaryPrimitives.WriteUInt64LittleEndian(this.AsSpan(this.AnOffset, 8), value);
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/SimplePropsOffsetMember/TestPacket.UShort1.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/SimplePropsOffsetMember/TestPacket.UShort1.g.cs
new file mode 100644
index 0000000..53e7093
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/SimplePropsOffsetMember/TestPacket.UShort1.g.cs
@@ -0,0 +1,19 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+partial class TestPacket
+{
+ protected partial ushort UShort1
+ {
+ get
+ {
+ return BinaryPrimitives.ReadUInt16LittleEndian(this.AsSpan(this.AnOffset, 2));
+ }
+ set
+ {
+ BinaryPrimitives.WriteUInt16LittleEndian(this.AsSpan(this.AnOffset, 2), value);
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/SimplePropsOffsetMember/TestPacket.cs b/RyuSocks.Generator.Test/data/PacketGenerator/SimplePropsOffsetMember/TestPacket.cs
new file mode 100644
index 0000000..92f76aa
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/SimplePropsOffsetMember/TestPacket.cs
@@ -0,0 +1,43 @@
+using RyuSocks.Packets;
+
+internal partial class TestPacket : Packet
+{
+ private int AnOffset => 2;
+
+ protected int AStringByteLength => 2;
+
+ public int ReadWriteByteLength { get; set; } = 34;
+
+ [PacketField(nameof(AnOffset))]
+ public partial byte Byte1 { get; set; }
+
+ [PacketField(nameof(AnOffset))]
+ internal partial sbyte SByte1 { get; set; }
+
+ [PacketField(nameof(AnOffset))]
+ protected partial ushort UShort1 { get; set; }
+
+ [PacketField(nameof(AnOffset))]
+ private partial short Short1 { get; set; }
+
+ [PacketField(nameof(AnOffset))]
+ protected private partial uint UInt1 { get; set; }
+
+ [PacketField(nameof(AnOffset))]
+ partial int Int1 { get; set; }
+
+ [PacketField(nameof(AnOffset))]
+ partial ulong ULong1 { get; set; }
+
+ [PacketField(nameof(AnOffset))]
+ public partial long Long1 { get; set; }
+
+ [PacketField(nameof(AnOffset), Length = 10)]
+ private partial string String1 { get; set; }
+
+ [PacketField(nameof(AnOffset), LengthMember = nameof(AStringByteLength))]
+ partial string String2 { get; set; }
+
+ [PacketField(nameof(AnOffset), LengthMember = nameof(ReadWriteByteLength))]
+ protected partial string String3 { get; set; }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncoding.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncoding.g.cs
new file mode 100644
index 0000000..d0779c3
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncoding.g.cs
@@ -0,0 +1,11 @@
+namespace RyuSocks.Packets
+{
+ public enum StringEncoding : byte
+ {
+ ASCII,
+ Unicode,
+ UTF7,
+ UTF8,
+ UTF32,
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.ASCIIPacket.String1.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.ASCIIPacket.String1.g.cs
new file mode 100644
index 0000000..e323721
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.ASCIIPacket.String1.g.cs
@@ -0,0 +1,22 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace BigEndian
+{
+ partial class ASCIIPacket
+ {
+ private partial string String1
+ {
+ get
+ {
+ return Encoding.ASCII.GetString(this.AsSpan(2, 10));
+ }
+ set
+ {
+ Encoding.ASCII.GetBytes(value, this.AsSpan(2, 10));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.ASCIIPacket.String2.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.ASCIIPacket.String2.g.cs
new file mode 100644
index 0000000..fed5363
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.ASCIIPacket.String2.g.cs
@@ -0,0 +1,26 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace BigEndian
+{
+ partial class ASCIIPacket
+ {
+ partial string String2
+ {
+ get
+ {
+ return Encoding.ASCII.GetString(this.AsSpan(12, this.AStringByteLength));
+ }
+ set
+ {
+ if (Encoding.ASCII.GetByteCount(value) != this.AStringByteLength)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), Encoding.ASCII.GetByteCount(value), $"byte length of value must be equal to: {this.AStringByteLength}");
+ }
+ Encoding.ASCII.GetBytes(value, this.AsSpan(12, this.AStringByteLength));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.ASCIIPacket.String3.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.ASCIIPacket.String3.g.cs
new file mode 100644
index 0000000..a4df373
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.ASCIIPacket.String3.g.cs
@@ -0,0 +1,23 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace BigEndian
+{
+ partial class ASCIIPacket
+ {
+ protected partial string String3
+ {
+ get
+ {
+ return Encoding.ASCII.GetString(this.AsSpan(14, this.ReadWriteByteLength));
+ }
+ set
+ {
+ this.ReadWriteByteLength = Encoding.ASCII.GetByteCount(value);
+ Encoding.ASCII.GetBytes(value, this.AsSpan(14, this.ReadWriteByteLength));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.ASCIIPacket.String4.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.ASCIIPacket.String4.g.cs
new file mode 100644
index 0000000..e2085a6
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.ASCIIPacket.String4.g.cs
@@ -0,0 +1,30 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace BigEndian
+{
+ partial class ASCIIPacket
+ {
+ public partial string String4
+ {
+ get
+ {
+ return Encoding.ASCII.GetString(this.AsSpan(48, 6));
+ }
+ set
+ {
+ if (value.Length < 2)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value.Length), value.Length, $"{nameof(value.Length)} must be larger or equal to: {2}");
+ }
+ if (value.Length > 6)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value.Length), value.Length, $"{nameof(value.Length)} must be smaller or equal to: {6}");
+ }
+ Encoding.ASCII.GetBytes(value, this.AsSpan(48, 6));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UTF32Packet.String1.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UTF32Packet.String1.g.cs
new file mode 100644
index 0000000..b78f1a0
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UTF32Packet.String1.g.cs
@@ -0,0 +1,26 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace BigEndian
+{
+ partial class UTF32Packet
+ {
+ private partial string String1
+ {
+ get
+ {
+ byte[] stringArray = this.AsSpan(2, 10).ToArray();
+ Array.Reverse(stringArray);
+ return Encoding.UTF32.GetString(stringArray);
+ }
+ set
+ {
+ byte[] byteArray = Encoding.UTF32.GetBytes(value);
+ Array.Reverse(byteArray);
+ byteArray.CopyTo(this.AsSpan(2, 10));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UTF32Packet.String2.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UTF32Packet.String2.g.cs
new file mode 100644
index 0000000..1247f7c
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UTF32Packet.String2.g.cs
@@ -0,0 +1,30 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace BigEndian
+{
+ partial class UTF32Packet
+ {
+ partial string String2
+ {
+ get
+ {
+ byte[] stringArray = this.AsSpan(12, this.AStringByteLength).ToArray();
+ Array.Reverse(stringArray);
+ return Encoding.UTF32.GetString(stringArray);
+ }
+ set
+ {
+ if (Encoding.UTF32.GetByteCount(value) != this.AStringByteLength)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), Encoding.UTF32.GetByteCount(value), $"byte length of value must be equal to: {this.AStringByteLength}");
+ }
+ byte[] byteArray = Encoding.UTF32.GetBytes(value);
+ Array.Reverse(byteArray);
+ byteArray.CopyTo(this.AsSpan(12, this.AStringByteLength));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UTF32Packet.String3.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UTF32Packet.String3.g.cs
new file mode 100644
index 0000000..d601008
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UTF32Packet.String3.g.cs
@@ -0,0 +1,27 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace BigEndian
+{
+ partial class UTF32Packet
+ {
+ protected partial string String3
+ {
+ get
+ {
+ byte[] stringArray = this.AsSpan(14, this.ReadWriteByteLength).ToArray();
+ Array.Reverse(stringArray);
+ return Encoding.UTF32.GetString(stringArray);
+ }
+ set
+ {
+ this.ReadWriteByteLength = Encoding.UTF32.GetByteCount(value);
+ byte[] byteArray = Encoding.UTF32.GetBytes(value);
+ Array.Reverse(byteArray);
+ byteArray.CopyTo(this.AsSpan(14, this.ReadWriteByteLength));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UTF32Packet.String4.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UTF32Packet.String4.g.cs
new file mode 100644
index 0000000..1e8c6d3
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UTF32Packet.String4.g.cs
@@ -0,0 +1,34 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace BigEndian
+{
+ partial class UTF32Packet
+ {
+ public partial string String4
+ {
+ get
+ {
+ byte[] stringArray = this.AsSpan(48, 6).ToArray();
+ Array.Reverse(stringArray);
+ return Encoding.UTF32.GetString(stringArray);
+ }
+ set
+ {
+ if (value.Length < 2)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value.Length), value.Length, $"{nameof(value.Length)} must be larger or equal to: {2}");
+ }
+ if (value.Length > 6)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value.Length), value.Length, $"{nameof(value.Length)} must be smaller or equal to: {6}");
+ }
+ byte[] byteArray = Encoding.UTF32.GetBytes(value);
+ Array.Reverse(byteArray);
+ byteArray.CopyTo(this.AsSpan(48, 6));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UTF7Packet.String1.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UTF7Packet.String1.g.cs
new file mode 100644
index 0000000..377ad77
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UTF7Packet.String1.g.cs
@@ -0,0 +1,22 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace BigEndian
+{
+ partial class UTF7Packet
+ {
+ private partial string String1
+ {
+ get
+ {
+ return Encoding.UTF7.GetString(this.AsSpan(2, 10));
+ }
+ set
+ {
+ Encoding.UTF7.GetBytes(value, this.AsSpan(2, 10));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UTF7Packet.String2.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UTF7Packet.String2.g.cs
new file mode 100644
index 0000000..b5647b6
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UTF7Packet.String2.g.cs
@@ -0,0 +1,26 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace BigEndian
+{
+ partial class UTF7Packet
+ {
+ partial string String2
+ {
+ get
+ {
+ return Encoding.UTF7.GetString(this.AsSpan(12, this.AStringByteLength));
+ }
+ set
+ {
+ if (Encoding.UTF7.GetByteCount(value) != this.AStringByteLength)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), Encoding.UTF7.GetByteCount(value), $"byte length of value must be equal to: {this.AStringByteLength}");
+ }
+ Encoding.UTF7.GetBytes(value, this.AsSpan(12, this.AStringByteLength));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UTF7Packet.String3.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UTF7Packet.String3.g.cs
new file mode 100644
index 0000000..9f91800
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UTF7Packet.String3.g.cs
@@ -0,0 +1,23 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace BigEndian
+{
+ partial class UTF7Packet
+ {
+ protected partial string String3
+ {
+ get
+ {
+ return Encoding.UTF7.GetString(this.AsSpan(14, this.ReadWriteByteLength));
+ }
+ set
+ {
+ this.ReadWriteByteLength = Encoding.UTF7.GetByteCount(value);
+ Encoding.UTF7.GetBytes(value, this.AsSpan(14, this.ReadWriteByteLength));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UTF7Packet.String4.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UTF7Packet.String4.g.cs
new file mode 100644
index 0000000..ba839a3
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UTF7Packet.String4.g.cs
@@ -0,0 +1,30 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace BigEndian
+{
+ partial class UTF7Packet
+ {
+ public partial string String4
+ {
+ get
+ {
+ return Encoding.UTF7.GetString(this.AsSpan(48, 6));
+ }
+ set
+ {
+ if (value.Length < 2)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value.Length), value.Length, $"{nameof(value.Length)} must be larger or equal to: {2}");
+ }
+ if (value.Length > 6)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value.Length), value.Length, $"{nameof(value.Length)} must be smaller or equal to: {6}");
+ }
+ Encoding.UTF7.GetBytes(value, this.AsSpan(48, 6));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UTF8Packet.String1.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UTF8Packet.String1.g.cs
new file mode 100644
index 0000000..03ce374
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UTF8Packet.String1.g.cs
@@ -0,0 +1,22 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace BigEndian
+{
+ partial class UTF8Packet
+ {
+ private partial string String1
+ {
+ get
+ {
+ return Encoding.UTF8.GetString(this.AsSpan(2, 10));
+ }
+ set
+ {
+ Encoding.UTF8.GetBytes(value, this.AsSpan(2, 10));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UTF8Packet.String2.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UTF8Packet.String2.g.cs
new file mode 100644
index 0000000..74100ee
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UTF8Packet.String2.g.cs
@@ -0,0 +1,26 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace BigEndian
+{
+ partial class UTF8Packet
+ {
+ partial string String2
+ {
+ get
+ {
+ return Encoding.UTF8.GetString(this.AsSpan(12, this.AStringByteLength));
+ }
+ set
+ {
+ if (Encoding.UTF8.GetByteCount(value) != this.AStringByteLength)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), Encoding.UTF8.GetByteCount(value), $"byte length of value must be equal to: {this.AStringByteLength}");
+ }
+ Encoding.UTF8.GetBytes(value, this.AsSpan(12, this.AStringByteLength));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UTF8Packet.String3.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UTF8Packet.String3.g.cs
new file mode 100644
index 0000000..178c41d
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UTF8Packet.String3.g.cs
@@ -0,0 +1,23 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace BigEndian
+{
+ partial class UTF8Packet
+ {
+ protected partial string String3
+ {
+ get
+ {
+ return Encoding.UTF8.GetString(this.AsSpan(14, this.ReadWriteByteLength));
+ }
+ set
+ {
+ this.ReadWriteByteLength = Encoding.UTF8.GetByteCount(value);
+ Encoding.UTF8.GetBytes(value, this.AsSpan(14, this.ReadWriteByteLength));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UTF8Packet.String4.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UTF8Packet.String4.g.cs
new file mode 100644
index 0000000..4a85877
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UTF8Packet.String4.g.cs
@@ -0,0 +1,30 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace BigEndian
+{
+ partial class UTF8Packet
+ {
+ public partial string String4
+ {
+ get
+ {
+ return Encoding.UTF8.GetString(this.AsSpan(48, 6));
+ }
+ set
+ {
+ if (value.Length < 2)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value.Length), value.Length, $"{nameof(value.Length)} must be larger or equal to: {2}");
+ }
+ if (value.Length > 6)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value.Length), value.Length, $"{nameof(value.Length)} must be smaller or equal to: {6}");
+ }
+ Encoding.UTF8.GetBytes(value, this.AsSpan(48, 6));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UnicodePacket.String1.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UnicodePacket.String1.g.cs
new file mode 100644
index 0000000..7e2d0d5
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UnicodePacket.String1.g.cs
@@ -0,0 +1,22 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace BigEndian
+{
+ partial class UnicodePacket
+ {
+ private partial string String1
+ {
+ get
+ {
+ return Encoding.BigEndianUnicode.GetString(this.AsSpan(2, 10));
+ }
+ set
+ {
+ Encoding.BigEndianUnicode.GetBytes(value, this.AsSpan(2, 10));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UnicodePacket.String2.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UnicodePacket.String2.g.cs
new file mode 100644
index 0000000..71ed10b
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UnicodePacket.String2.g.cs
@@ -0,0 +1,26 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace BigEndian
+{
+ partial class UnicodePacket
+ {
+ partial string String2
+ {
+ get
+ {
+ return Encoding.BigEndianUnicode.GetString(this.AsSpan(12, this.AStringByteLength));
+ }
+ set
+ {
+ if (Encoding.Unicode.GetByteCount(value) != this.AStringByteLength)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), Encoding.Unicode.GetByteCount(value), $"byte length of value must be equal to: {this.AStringByteLength}");
+ }
+ Encoding.BigEndianUnicode.GetBytes(value, this.AsSpan(12, this.AStringByteLength));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UnicodePacket.String3.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UnicodePacket.String3.g.cs
new file mode 100644
index 0000000..ecbd7c8
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UnicodePacket.String3.g.cs
@@ -0,0 +1,23 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace BigEndian
+{
+ partial class UnicodePacket
+ {
+ protected partial string String3
+ {
+ get
+ {
+ return Encoding.BigEndianUnicode.GetString(this.AsSpan(14, this.ReadWriteByteLength));
+ }
+ set
+ {
+ this.ReadWriteByteLength = Encoding.Unicode.GetByteCount(value);
+ Encoding.BigEndianUnicode.GetBytes(value, this.AsSpan(14, this.ReadWriteByteLength));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UnicodePacket.String4.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UnicodePacket.String4.g.cs
new file mode 100644
index 0000000..71b7846
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/BigEndian.UnicodePacket.String4.g.cs
@@ -0,0 +1,30 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace BigEndian
+{
+ partial class UnicodePacket
+ {
+ public partial string String4
+ {
+ get
+ {
+ return Encoding.BigEndianUnicode.GetString(this.AsSpan(48, 6));
+ }
+ set
+ {
+ if (value.Length < 2)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value.Length), value.Length, $"{nameof(value.Length)} must be larger or equal to: {2}");
+ }
+ if (value.Length > 6)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value.Length), value.Length, $"{nameof(value.Length)} must be smaller or equal to: {6}");
+ }
+ Encoding.BigEndianUnicode.GetBytes(value, this.AsSpan(48, 6));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.ASCIIPacket.String1.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.ASCIIPacket.String1.g.cs
new file mode 100644
index 0000000..a435828
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.ASCIIPacket.String1.g.cs
@@ -0,0 +1,22 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace LittleEndian
+{
+ partial class ASCIIPacket
+ {
+ private partial string String1
+ {
+ get
+ {
+ return Encoding.ASCII.GetString(this.AsSpan(2, 10));
+ }
+ set
+ {
+ Encoding.ASCII.GetBytes(value, this.AsSpan(2, 10));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.ASCIIPacket.String2.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.ASCIIPacket.String2.g.cs
new file mode 100644
index 0000000..c1cc8e2
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.ASCIIPacket.String2.g.cs
@@ -0,0 +1,26 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace LittleEndian
+{
+ partial class ASCIIPacket
+ {
+ partial string String2
+ {
+ get
+ {
+ return Encoding.ASCII.GetString(this.AsSpan(12, this.AStringByteLength));
+ }
+ set
+ {
+ if (Encoding.ASCII.GetByteCount(value) != this.AStringByteLength)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), Encoding.ASCII.GetByteCount(value), $"byte length of value must be equal to: {this.AStringByteLength}");
+ }
+ Encoding.ASCII.GetBytes(value, this.AsSpan(12, this.AStringByteLength));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.ASCIIPacket.String3.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.ASCIIPacket.String3.g.cs
new file mode 100644
index 0000000..419b0f8
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.ASCIIPacket.String3.g.cs
@@ -0,0 +1,23 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace LittleEndian
+{
+ partial class ASCIIPacket
+ {
+ protected partial string String3
+ {
+ get
+ {
+ return Encoding.ASCII.GetString(this.AsSpan(14, this.ReadWriteByteLength));
+ }
+ set
+ {
+ this.ReadWriteByteLength = Encoding.ASCII.GetByteCount(value);
+ Encoding.ASCII.GetBytes(value, this.AsSpan(14, this.ReadWriteByteLength));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.ASCIIPacket.String4.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.ASCIIPacket.String4.g.cs
new file mode 100644
index 0000000..3580a23
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.ASCIIPacket.String4.g.cs
@@ -0,0 +1,30 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace LittleEndian
+{
+ partial class ASCIIPacket
+ {
+ public partial string String4
+ {
+ get
+ {
+ return Encoding.ASCII.GetString(this.AsSpan(48, 6));
+ }
+ set
+ {
+ if (value.Length < 2)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value.Length), value.Length, $"{nameof(value.Length)} must be larger or equal to: {2}");
+ }
+ if (value.Length > 6)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value.Length), value.Length, $"{nameof(value.Length)} must be smaller or equal to: {6}");
+ }
+ Encoding.ASCII.GetBytes(value, this.AsSpan(48, 6));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UTF32Packet.String1.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UTF32Packet.String1.g.cs
new file mode 100644
index 0000000..f8fe2b7
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UTF32Packet.String1.g.cs
@@ -0,0 +1,22 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace LittleEndian
+{
+ partial class UTF32Packet
+ {
+ private partial string String1
+ {
+ get
+ {
+ return Encoding.UTF32.GetString(this.AsSpan(2, 10));
+ }
+ set
+ {
+ Encoding.UTF32.GetBytes(value, this.AsSpan(2, 10));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UTF32Packet.String2.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UTF32Packet.String2.g.cs
new file mode 100644
index 0000000..3d6d753
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UTF32Packet.String2.g.cs
@@ -0,0 +1,26 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace LittleEndian
+{
+ partial class UTF32Packet
+ {
+ partial string String2
+ {
+ get
+ {
+ return Encoding.UTF32.GetString(this.AsSpan(12, this.AStringByteLength));
+ }
+ set
+ {
+ if (Encoding.UTF32.GetByteCount(value) != this.AStringByteLength)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), Encoding.UTF32.GetByteCount(value), $"byte length of value must be equal to: {this.AStringByteLength}");
+ }
+ Encoding.UTF32.GetBytes(value, this.AsSpan(12, this.AStringByteLength));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UTF32Packet.String3.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UTF32Packet.String3.g.cs
new file mode 100644
index 0000000..721c873
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UTF32Packet.String3.g.cs
@@ -0,0 +1,23 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace LittleEndian
+{
+ partial class UTF32Packet
+ {
+ protected partial string String3
+ {
+ get
+ {
+ return Encoding.UTF32.GetString(this.AsSpan(14, this.ReadWriteByteLength));
+ }
+ set
+ {
+ this.ReadWriteByteLength = Encoding.UTF32.GetByteCount(value);
+ Encoding.UTF32.GetBytes(value, this.AsSpan(14, this.ReadWriteByteLength));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UTF32Packet.String4.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UTF32Packet.String4.g.cs
new file mode 100644
index 0000000..adcefd0
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UTF32Packet.String4.g.cs
@@ -0,0 +1,30 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace LittleEndian
+{
+ partial class UTF32Packet
+ {
+ public partial string String4
+ {
+ get
+ {
+ return Encoding.UTF32.GetString(this.AsSpan(48, 6));
+ }
+ set
+ {
+ if (value.Length < 2)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value.Length), value.Length, $"{nameof(value.Length)} must be larger or equal to: {2}");
+ }
+ if (value.Length > 6)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value.Length), value.Length, $"{nameof(value.Length)} must be smaller or equal to: {6}");
+ }
+ Encoding.UTF32.GetBytes(value, this.AsSpan(48, 6));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UTF7Packet.String1.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UTF7Packet.String1.g.cs
new file mode 100644
index 0000000..7b13be0
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UTF7Packet.String1.g.cs
@@ -0,0 +1,22 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace LittleEndian
+{
+ partial class UTF7Packet
+ {
+ private partial string String1
+ {
+ get
+ {
+ return Encoding.UTF7.GetString(this.AsSpan(2, 10));
+ }
+ set
+ {
+ Encoding.UTF7.GetBytes(value, this.AsSpan(2, 10));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UTF7Packet.String2.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UTF7Packet.String2.g.cs
new file mode 100644
index 0000000..cece67b
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UTF7Packet.String2.g.cs
@@ -0,0 +1,26 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace LittleEndian
+{
+ partial class UTF7Packet
+ {
+ partial string String2
+ {
+ get
+ {
+ return Encoding.UTF7.GetString(this.AsSpan(12, this.AStringByteLength));
+ }
+ set
+ {
+ if (Encoding.UTF7.GetByteCount(value) != this.AStringByteLength)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), Encoding.UTF7.GetByteCount(value), $"byte length of value must be equal to: {this.AStringByteLength}");
+ }
+ Encoding.UTF7.GetBytes(value, this.AsSpan(12, this.AStringByteLength));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UTF7Packet.String3.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UTF7Packet.String3.g.cs
new file mode 100644
index 0000000..2dc995b
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UTF7Packet.String3.g.cs
@@ -0,0 +1,23 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace LittleEndian
+{
+ partial class UTF7Packet
+ {
+ protected partial string String3
+ {
+ get
+ {
+ return Encoding.UTF7.GetString(this.AsSpan(14, this.ReadWriteByteLength));
+ }
+ set
+ {
+ this.ReadWriteByteLength = Encoding.UTF7.GetByteCount(value);
+ Encoding.UTF7.GetBytes(value, this.AsSpan(14, this.ReadWriteByteLength));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UTF7Packet.String4.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UTF7Packet.String4.g.cs
new file mode 100644
index 0000000..331fe27
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UTF7Packet.String4.g.cs
@@ -0,0 +1,30 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace LittleEndian
+{
+ partial class UTF7Packet
+ {
+ public partial string String4
+ {
+ get
+ {
+ return Encoding.UTF7.GetString(this.AsSpan(48, 6));
+ }
+ set
+ {
+ if (value.Length < 2)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value.Length), value.Length, $"{nameof(value.Length)} must be larger or equal to: {2}");
+ }
+ if (value.Length > 6)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value.Length), value.Length, $"{nameof(value.Length)} must be smaller or equal to: {6}");
+ }
+ Encoding.UTF7.GetBytes(value, this.AsSpan(48, 6));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UTF8Packet.String1.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UTF8Packet.String1.g.cs
new file mode 100644
index 0000000..a5e94ca
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UTF8Packet.String1.g.cs
@@ -0,0 +1,22 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace LittleEndian
+{
+ partial class UTF8Packet
+ {
+ private partial string String1
+ {
+ get
+ {
+ return Encoding.UTF8.GetString(this.AsSpan(2, 10));
+ }
+ set
+ {
+ Encoding.UTF8.GetBytes(value, this.AsSpan(2, 10));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UTF8Packet.String2.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UTF8Packet.String2.g.cs
new file mode 100644
index 0000000..b7ee39e
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UTF8Packet.String2.g.cs
@@ -0,0 +1,26 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace LittleEndian
+{
+ partial class UTF8Packet
+ {
+ partial string String2
+ {
+ get
+ {
+ return Encoding.UTF8.GetString(this.AsSpan(12, this.AStringByteLength));
+ }
+ set
+ {
+ if (Encoding.UTF8.GetByteCount(value) != this.AStringByteLength)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), Encoding.UTF8.GetByteCount(value), $"byte length of value must be equal to: {this.AStringByteLength}");
+ }
+ Encoding.UTF8.GetBytes(value, this.AsSpan(12, this.AStringByteLength));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UTF8Packet.String3.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UTF8Packet.String3.g.cs
new file mode 100644
index 0000000..eeef44f
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UTF8Packet.String3.g.cs
@@ -0,0 +1,23 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace LittleEndian
+{
+ partial class UTF8Packet
+ {
+ protected partial string String3
+ {
+ get
+ {
+ return Encoding.UTF8.GetString(this.AsSpan(14, this.ReadWriteByteLength));
+ }
+ set
+ {
+ this.ReadWriteByteLength = Encoding.UTF8.GetByteCount(value);
+ Encoding.UTF8.GetBytes(value, this.AsSpan(14, this.ReadWriteByteLength));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UTF8Packet.String4.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UTF8Packet.String4.g.cs
new file mode 100644
index 0000000..87d82fd
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UTF8Packet.String4.g.cs
@@ -0,0 +1,30 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace LittleEndian
+{
+ partial class UTF8Packet
+ {
+ public partial string String4
+ {
+ get
+ {
+ return Encoding.UTF8.GetString(this.AsSpan(48, 6));
+ }
+ set
+ {
+ if (value.Length < 2)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value.Length), value.Length, $"{nameof(value.Length)} must be larger or equal to: {2}");
+ }
+ if (value.Length > 6)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value.Length), value.Length, $"{nameof(value.Length)} must be smaller or equal to: {6}");
+ }
+ Encoding.UTF8.GetBytes(value, this.AsSpan(48, 6));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UnicodePacket.String1.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UnicodePacket.String1.g.cs
new file mode 100644
index 0000000..ba79c02
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UnicodePacket.String1.g.cs
@@ -0,0 +1,22 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace LittleEndian
+{
+ partial class UnicodePacket
+ {
+ private partial string String1
+ {
+ get
+ {
+ return Encoding.Unicode.GetString(this.AsSpan(2, 10));
+ }
+ set
+ {
+ Encoding.Unicode.GetBytes(value, this.AsSpan(2, 10));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UnicodePacket.String2.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UnicodePacket.String2.g.cs
new file mode 100644
index 0000000..fea8e56
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UnicodePacket.String2.g.cs
@@ -0,0 +1,26 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace LittleEndian
+{
+ partial class UnicodePacket
+ {
+ partial string String2
+ {
+ get
+ {
+ return Encoding.Unicode.GetString(this.AsSpan(12, this.AStringByteLength));
+ }
+ set
+ {
+ if (Encoding.Unicode.GetByteCount(value) != this.AStringByteLength)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), Encoding.Unicode.GetByteCount(value), $"byte length of value must be equal to: {this.AStringByteLength}");
+ }
+ Encoding.Unicode.GetBytes(value, this.AsSpan(12, this.AStringByteLength));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UnicodePacket.String3.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UnicodePacket.String3.g.cs
new file mode 100644
index 0000000..103e358
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UnicodePacket.String3.g.cs
@@ -0,0 +1,23 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace LittleEndian
+{
+ partial class UnicodePacket
+ {
+ protected partial string String3
+ {
+ get
+ {
+ return Encoding.Unicode.GetString(this.AsSpan(14, this.ReadWriteByteLength));
+ }
+ set
+ {
+ this.ReadWriteByteLength = Encoding.Unicode.GetByteCount(value);
+ Encoding.Unicode.GetBytes(value, this.AsSpan(14, this.ReadWriteByteLength));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UnicodePacket.String4.g.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UnicodePacket.String4.g.cs
new file mode 100644
index 0000000..5d7ee9a
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/LittleEndian.UnicodePacket.String4.g.cs
@@ -0,0 +1,30 @@
+using RyuSocks.Packets;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace LittleEndian
+{
+ partial class UnicodePacket
+ {
+ public partial string String4
+ {
+ get
+ {
+ return Encoding.Unicode.GetString(this.AsSpan(48, 6));
+ }
+ set
+ {
+ if (value.Length < 2)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value.Length), value.Length, $"{nameof(value.Length)} must be larger or equal to: {2}");
+ }
+ if (value.Length > 6)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value.Length), value.Length, $"{nameof(value.Length)} must be smaller or equal to: {6}");
+ }
+ Encoding.Unicode.GetBytes(value, this.AsSpan(48, 6));
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/TestPacket.cs b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/TestPacket.cs
new file mode 100644
index 0000000..d8a04a8
--- /dev/null
+++ b/RyuSocks.Generator.Test/data/PacketGenerator/StringEncodings/TestPacket.cs
@@ -0,0 +1,198 @@
+using RyuSocks.Packets;
+
+namespace LittleEndian
+{
+ public partial class ASCIIPacket : Packet
+ {
+ protected int AStringByteLength => 2;
+
+ public int ReadWriteByteLength { get; set; } = 34;
+
+ [PacketField(2, Length = 10)]
+ private partial string String1 { get; set; }
+
+ [PacketField(12, LengthMember = nameof(AStringByteLength))]
+ partial string String2 { get; set; }
+
+ [PacketField(14, LengthMember = nameof(ReadWriteByteLength))]
+ protected partial string String3 { get; set; }
+
+ [PacketField(48, Length = 6, MinLength = 2, MaxLength = 6)]
+ public partial string String4 { get; set; }
+ }
+
+ public partial class UnicodePacket : Packet
+ {
+ protected int AStringByteLength => 2;
+
+ public int ReadWriteByteLength { get; set; } = 34;
+
+ [PacketField(2, Length = 10, StringEncoding = StringEncoding.Unicode)]
+ private partial string String1 { get; set; }
+
+ [PacketField(12, LengthMember = nameof(AStringByteLength), StringEncoding = StringEncoding.Unicode)]
+ partial string String2 { get; set; }
+
+ [PacketField(14, LengthMember = nameof(ReadWriteByteLength), StringEncoding = StringEncoding.Unicode)]
+ protected partial string String3 { get; set; }
+
+ [PacketField(48, Length = 6, MinLength = 2, MaxLength = 6, StringEncoding = StringEncoding.Unicode)]
+ public partial string String4 { get; set; }
+ }
+
+ public partial class UTF7Packet : Packet
+ {
+ protected int AStringByteLength => 2;
+
+ public int ReadWriteByteLength { get; set; } = 34;
+
+ [PacketField(2, Length = 10, StringEncoding = StringEncoding.UTF7)]
+ private partial string String1 { get; set; }
+
+ [PacketField(12, LengthMember = nameof(AStringByteLength), StringEncoding = StringEncoding.UTF7)]
+ partial string String2 { get; set; }
+
+ [PacketField(14, LengthMember = nameof(ReadWriteByteLength), StringEncoding = StringEncoding.UTF7)]
+ protected partial string String3 { get; set; }
+
+ [PacketField(48, Length = 6, MinLength = 2, MaxLength = 6, StringEncoding = StringEncoding.UTF7)]
+ public partial string String4 { get; set; }
+ }
+
+ public partial class UTF8Packet : Packet
+ {
+ protected int AStringByteLength => 2;
+
+ public int ReadWriteByteLength { get; set; } = 34;
+
+ [PacketField(2, Length = 10, StringEncoding = StringEncoding.UTF8)]
+ private partial string String1 { get; set; }
+
+ [PacketField(12, LengthMember = nameof(AStringByteLength), StringEncoding = StringEncoding.UTF8)]
+ partial string String2 { get; set; }
+
+ [PacketField(14, LengthMember = nameof(ReadWriteByteLength), StringEncoding = StringEncoding.UTF8)]
+ protected partial string String3 { get; set; }
+
+ [PacketField(48, Length = 6, MinLength = 2, MaxLength = 6, StringEncoding = StringEncoding.UTF8)]
+ public partial string String4 { get; set; }
+ }
+
+ public partial class UTF32Packet : Packet
+ {
+ protected int AStringByteLength => 2;
+
+ public int ReadWriteByteLength { get; set; } = 34;
+
+ [PacketField(2, Length = 10, StringEncoding = StringEncoding.UTF32)]
+ private partial string String1 { get; set; }
+
+ [PacketField(12, LengthMember = nameof(AStringByteLength), StringEncoding = StringEncoding.UTF32)]
+ partial string String2 { get; set; }
+
+ [PacketField(14, LengthMember = nameof(ReadWriteByteLength), StringEncoding = StringEncoding.UTF32)]
+ protected partial string String3 { get; set; }
+
+ [PacketField(48, Length = 6, MinLength = 2, MaxLength = 6, StringEncoding = StringEncoding.UTF32)]
+ public partial string String4 { get; set; }
+ }
+}
+
+namespace BigEndian
+{
+ public partial class ASCIIPacket : Packet
+ {
+ protected int AStringByteLength => 2;
+
+ public int ReadWriteByteLength { get; set; } = 34;
+
+ [PacketField(2, Length = 10, IsBigEndian = true)]
+ private partial string String1 { get; set; }
+
+ [PacketField(12, LengthMember = nameof(AStringByteLength), IsBigEndian = true)]
+ partial string String2 { get; set; }
+
+ [PacketField(14, LengthMember = nameof(ReadWriteByteLength), IsBigEndian = true)]
+ protected partial string String3 { get; set; }
+
+ [PacketField(48, Length = 6, MinLength = 2, MaxLength = 6, IsBigEndian = true)]
+ public partial string String4 { get; set; }
+ }
+
+ public partial class UnicodePacket : Packet
+ {
+ protected int AStringByteLength => 2;
+
+ public int ReadWriteByteLength { get; set; } = 34;
+
+ [PacketField(2, Length = 10, StringEncoding = StringEncoding.Unicode, IsBigEndian = true)]
+ private partial string String1 { get; set; }
+
+ [PacketField(12, LengthMember = nameof(AStringByteLength), StringEncoding = StringEncoding.Unicode, IsBigEndian = true)]
+ partial string String2 { get; set; }
+
+ [PacketField(14, LengthMember = nameof(ReadWriteByteLength), StringEncoding = StringEncoding.Unicode, IsBigEndian = true)]
+ protected partial string String3 { get; set; }
+
+ [PacketField(48, Length = 6, MinLength = 2, MaxLength = 6, StringEncoding = StringEncoding.Unicode, IsBigEndian = true)]
+ public partial string String4 { get; set; }
+ }
+
+ public partial class UTF7Packet : Packet
+ {
+ protected int AStringByteLength => 2;
+
+ public int ReadWriteByteLength { get; set; } = 34;
+
+ [PacketField(2, Length = 10, StringEncoding = StringEncoding.UTF7, IsBigEndian = true)]
+ private partial string String1 { get; set; }
+
+ [PacketField(12, LengthMember = nameof(AStringByteLength), StringEncoding = StringEncoding.UTF7, IsBigEndian = true)]
+ partial string String2 { get; set; }
+
+ [PacketField(14, LengthMember = nameof(ReadWriteByteLength), StringEncoding = StringEncoding.UTF7, IsBigEndian = true)]
+ protected partial string String3 { get; set; }
+
+ [PacketField(48, Length = 6, MinLength = 2, MaxLength = 6, StringEncoding = StringEncoding.UTF7, IsBigEndian = true)]
+ public partial string String4 { get; set; }
+ }
+
+ public partial class UTF8Packet : Packet
+ {
+ protected int AStringByteLength => 2;
+
+ public int ReadWriteByteLength { get; set; } = 34;
+
+ [PacketField(2, Length = 10, StringEncoding = StringEncoding.UTF8, IsBigEndian = true)]
+ private partial string String1 { get; set; }
+
+ [PacketField(12, LengthMember = nameof(AStringByteLength), StringEncoding = StringEncoding.UTF8, IsBigEndian = true)]
+ partial string String2 { get; set; }
+
+ [PacketField(14, LengthMember = nameof(ReadWriteByteLength), StringEncoding = StringEncoding.UTF8, IsBigEndian = true)]
+ protected partial string String3 { get; set; }
+
+ [PacketField(48, Length = 6, MinLength = 2, MaxLength = 6, StringEncoding = StringEncoding.UTF8, IsBigEndian = true)]
+ public partial string String4 { get; set; }
+ }
+
+ public partial class UTF32Packet : Packet
+ {
+ protected int AStringByteLength => 2;
+
+ public int ReadWriteByteLength { get; set; } = 34;
+
+ [PacketField(2, Length = 10, StringEncoding = StringEncoding.UTF32, IsBigEndian = true)]
+ private partial string String1 { get; set; }
+
+ [PacketField(12, LengthMember = nameof(AStringByteLength), StringEncoding = StringEncoding.UTF32, IsBigEndian = true)]
+ partial string String2 { get; set; }
+
+ [PacketField(14, LengthMember = nameof(ReadWriteByteLength), StringEncoding = StringEncoding.UTF32, IsBigEndian = true)]
+ protected partial string String3 { get; set; }
+
+ [PacketField(48, Length = 6, MinLength = 2, MaxLength = 6, StringEncoding = StringEncoding.UTF32, IsBigEndian = true)]
+ public partial string String4 { get; set; }
+ }
+}
+
diff --git a/RyuSocks.Generator/Builder/AbstractBuilder.cs b/RyuSocks.Generator/Builder/AbstractBuilder.cs
index 6c44ce2..bbe3638 100644
--- a/RyuSocks.Generator/Builder/AbstractBuilder.cs
+++ b/RyuSocks.Generator/Builder/AbstractBuilder.cs
@@ -1,5 +1,8 @@
+using System.Diagnostics.CodeAnalysis;
+
namespace RyuSocks.Generator.Builder
{
+ [ExcludeFromCodeCoverage]
abstract class AbstractBuilder
{
protected const int IndentLength = 4;
diff --git a/RyuSocks.Generator/Builder/BlockBuilder.cs b/RyuSocks.Generator/Builder/BlockBuilder.cs
index 8b3d144..4577e43 100644
--- a/RyuSocks.Generator/Builder/BlockBuilder.cs
+++ b/RyuSocks.Generator/Builder/BlockBuilder.cs
@@ -1,7 +1,9 @@
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
namespace RyuSocks.Generator.Builder
{
+ [ExcludeFromCodeCoverage]
class BlockBuilder : AbstractBuilder
{
private readonly List _block = [];
diff --git a/RyuSocks.Generator/Builder/CodeBuilder.cs b/RyuSocks.Generator/Builder/CodeBuilder.cs
index 7186ca8..2db7438 100644
--- a/RyuSocks.Generator/Builder/CodeBuilder.cs
+++ b/RyuSocks.Generator/Builder/CodeBuilder.cs
@@ -1,8 +1,10 @@
+using System.Diagnostics.CodeAnalysis;
using System.Text;
namespace RyuSocks.Generator.Builder
{
// Original source: https://github.com/Ryujinx/Ryujinx/blob/1df6c07f78c4c3b8c7fc679d7466f79a10c2d496/src/Ryujinx.Horizon.Generators/CodeGenerator.cs
+ [ExcludeFromCodeCoverage]
class CodeBuilder : AbstractBuilder
{
private readonly StringBuilder _sb = new();
diff --git a/RyuSocks.Generator/Packet/AccessorGenerator.Array.cs b/RyuSocks.Generator/Packet/AccessorGenerator.Array.cs
new file mode 100644
index 0000000..3896b5a
--- /dev/null
+++ b/RyuSocks.Generator/Packet/AccessorGenerator.Array.cs
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) RyuSOCKS
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using RyuSocks.Generator.Builder;
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+namespace RyuSocks.Generator.Packet
+{
+ [SuppressMessage("ReSharper", "RedundantIfElseBlock")]
+ internal static partial class AccessorGenerator
+ {
+ private static string GenerateArrayByteAccessor(PacketFieldModel packetField, bool isGetter)
+ {
+ if (isGetter)
+ {
+ string fieldTypeName = packetField.FieldType.Name.Extract(0, -2);
+ string maybeCast = packetField.FieldType.IsEnum ? $"({fieldTypeName})" : string.Empty;
+
+ return $"result[i] = {maybeCast}this[{packetField.GetOffset()} + i];";
+ }
+ else
+ {
+ string maybeCastValue = packetField.FieldType.IsEnum ? "(byte)" : string.Empty;
+
+ return $"this[{packetField.GetOffset()} + i] = {maybeCastValue}value[i];";
+ }
+ }
+
+ private static string GenerateArraySByteAccessor(PacketFieldModel packetField, bool isGetter)
+ {
+ if (isGetter)
+ {
+ string fieldTypeName = packetField.FieldType.Name.Extract(0, -2);
+
+ return $"result[i] = ({fieldTypeName})this[{packetField.GetOffset()} + i];";
+ }
+ else
+ {
+ return $"this[{packetField.GetOffset()} + i] = (byte)value[i];";
+ }
+ }
+
+ private static string GenerateArrayIntegralAccessor(PacketFieldModel packetField, TypeConverterModel converter, bool isGetter)
+ {
+ if (isGetter)
+ {
+ string fieldTypeName = packetField.FieldType.Name.Extract(0, -2);
+ string maybeCast = packetField.FieldType.IsEnum ? $"({fieldTypeName})" : string.Empty;
+
+ return $"result[i] = {maybeCast}{converter.GetReaderName(packetField.IsBigEndian)}(this.AsSpan({packetField.GetOffset()} + (i * {converter.Length}), {converter.Length}));";
+ }
+ else
+ {
+ string valueParameter = packetField.FieldType.IsEnum ? $"({packetField.FieldType.ActualType.ToTypeString()})value" : "value";
+
+ return $"{converter.GetWriterName(packetField.IsBigEndian)}(this.AsSpan({packetField.GetOffset()} + (i * {converter.Length}), {converter.Length}), {valueParameter}[i]);";
+ }
+ }
+
+ private static string[] GenerateSimpleArrayAccessor(PacketFieldModel packetField, bool isGetter)
+ {
+ BlockBuilder source = new();
+
+ if (isGetter)
+ {
+ string fieldTypeName = packetField.FieldType.Name.Extract(0, -2);
+
+ source.AppendLine($"{fieldTypeName}[] result = new {fieldTypeName}[{packetField.GetLength()}];");
+ source.AppendLine();
+ }
+
+ source.EnterScope($"for (int i = 0; i < {packetField.GetLength()}; i++)");
+
+ switch (packetField.FieldType.ActualType)
+ {
+ case ActualType.Byte:
+ source.AppendLine(GenerateArrayByteAccessor(packetField, isGetter));
+ break;
+ case ActualType.SByte:
+ source.AppendLine(GenerateArraySByteAccessor(packetField, isGetter));
+ break;
+ case ActualType.Int16:
+ case ActualType.UInt16:
+ case ActualType.Int32:
+ case ActualType.UInt32:
+ case ActualType.Int64:
+ case ActualType.UInt64:
+ var converter = TypeConverter.Map[packetField.FieldType.ActualType];
+ source.AppendLine(GenerateArrayIntegralAccessor(packetField, converter, isGetter));
+ break;
+ case ActualType.NamedType:
+ // Only deal with strings here
+ // TODO: Deal with strings
+ // return GenerateArrayStringAccessor(packetField, isGetter);
+ default:
+ throw new InvalidOperationException($"Unable to generate array accessor for type: {packetField.FieldType.Name}({packetField.FieldType.ActualType})");
+ }
+
+ source.LeaveScope();
+
+ if (isGetter)
+ {
+ source.AppendLine();
+ source.AppendLine("return result;");
+ }
+
+ return source.GetLines();
+ }
+
+ private static string[] GenerateArrayAccessor(PacketFieldModel packetField, bool isGetter)
+ {
+ BlockBuilder source = new();
+
+ if (!isGetter)
+ {
+ if (packetField.MinLength > 0)
+ {
+ ExceptionHelper.ArgumentOutOfRange.GenerateThrowIfLessThan(source, "value.Length", packetField.MinLength.ToString());
+ }
+ if (packetField.MaxLength > 0)
+ {
+ ExceptionHelper.ArgumentOutOfRange.GenerateThrowIfGreaterThan(source, "value.Length", packetField.MaxLength.ToString());
+ }
+ }
+
+ AddVerificationMethodIfNecessary(source, packetField, isGetter);
+
+ if (!isGetter)
+ {
+ if (packetField is { Length: <= 0, LengthMemberPermissions: Permissions.ReadWrite })
+ {
+ string maybeLengthMemberCast = packetField.LengthMemberType != ActualType.Int32 ? $"({packetField.LengthMemberType.ToTypeString()})" : string.Empty;
+ source.AppendLine($"this.{packetField.LengthMember} = {maybeLengthMemberCast}value.Length;");
+ }
+ else if (packetField is { Length: <= 0, LengthMemberPermissions: Permissions.ReadOnly })
+ {
+ ExceptionHelper.ArgumentOutOfRange.GenerateThrowIfNotEqual(source, "value.Length", $"this.{packetField.LengthMember}");
+ }
+
+ source.AppendLine();
+ }
+
+ if (packetField.FieldType.IsStruct)
+ {
+ // TODO: Deal with struct arrays
+ source.AppendLine("// TODO: struct arrays");
+ }
+ // Deal with simple types: enums, strings and integral numeric types
+ else if (packetField.FieldType is { ActualType: ActualType.NamedType, Name: "string" }
+ || packetField.FieldType.ActualType != ActualType.NamedType)
+ {
+ source.AppendBlock(GenerateSimpleArrayAccessor(packetField, isGetter));
+ }
+ else if (packetField.FieldType.ActualType == ActualType.NamedType)
+ {
+ // TODO: Deal with class arrays
+ source.AppendLine("// TODO: class arrays");
+ }
+ else
+ {
+ throw new InvalidOperationException($"Unknown property type. Unable to generate array accessor for: {packetField.FieldType.Name}({packetField.FieldType.ActualType})");
+ }
+
+ return source.GetLines();
+ }
+ }
+}
diff --git a/RyuSocks.Generator/Packet/AccessorGenerator.Class.cs b/RyuSocks.Generator/Packet/AccessorGenerator.Class.cs
new file mode 100644
index 0000000..ae61b08
--- /dev/null
+++ b/RyuSocks.Generator/Packet/AccessorGenerator.Class.cs
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) RyuSOCKS
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using RyuSocks.Generator.Builder;
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+namespace RyuSocks.Generator.Packet
+{
+ [SuppressMessage("ReSharper", "RedundantIfElseBlock")]
+ internal static partial class AccessorGenerator
+ {
+ private static string[] GenerateIPAddressAccessor(PacketFieldModel packetField, bool isGetter)
+ {
+ BlockBuilder source = new();
+
+ AddVerificationMethodIfNecessary(source, packetField, isGetter);
+
+ if (isGetter)
+ {
+ source.AppendLine($"return new System.Net.IPAddress(this.AsSpan({packetField.GetOffset()}, {packetField.GetLength()}));");
+ }
+ else
+ {
+ source.AppendLine($"value.GetAddressBytes().CopyTo(this.AsSpan({packetField.GetOffset()}, {packetField.GetLength()}));");
+ }
+
+ return source.GetLines();
+ }
+
+ private static string[] GenerateClassAccessor(PacketFieldModel packetField, bool isGetter)
+ {
+ switch (packetField.FieldType.Name)
+ {
+ case "System.Net.IPAddress":
+ return GenerateIPAddressAccessor(packetField, isGetter);
+ default:
+ // TODO: Deal with classes without relying on special handling
+ // - getter: Require a specific constructor?
+ // - setter: Require a specific method/property to get access to a Span or byte[]?
+ throw new InvalidOperationException($"Unknown property class type. Unable to generate accessor for: {packetField.FieldType.Name}({packetField.FieldType.ActualType})");
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator/Packet/AccessorGenerator.Simple.cs b/RyuSocks.Generator/Packet/AccessorGenerator.Simple.cs
new file mode 100644
index 0000000..b96bc54
--- /dev/null
+++ b/RyuSocks.Generator/Packet/AccessorGenerator.Simple.cs
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) RyuSOCKS
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using RyuSocks.Generator.Builder;
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+namespace RyuSocks.Generator.Packet
+{
+ [SuppressMessage("ReSharper", "RedundantIfElseBlock")]
+ internal static partial class AccessorGenerator
+ {
+ private static string GenerateSimpleByteAccessor(PacketFieldModel packetField, bool isGetter)
+ {
+ if (isGetter)
+ {
+ string maybeCast = packetField.FieldType.IsEnum ? $"({packetField.FieldType.Name})" : string.Empty;
+
+ return $"return {maybeCast}this[{packetField.GetOffset()}];";
+ }
+ else
+ {
+ string maybeCastValue = packetField.FieldType.IsEnum ? "(byte)" : string.Empty;
+
+ return $"this[{packetField.GetOffset()}] = {maybeCastValue}value;";
+ }
+ }
+
+ private static string GenerateSimpleSByteAccessor(PacketFieldModel packetField, bool isGetter)
+ {
+ if (isGetter)
+ {
+ string maybeCast = packetField.FieldType.IsEnum ? $"({packetField.FieldType.Name})" : string.Empty;
+
+ return $"return {maybeCast}(sbyte)this[{packetField.GetOffset()}];";
+ }
+ else
+ {
+ return $"this[{packetField.GetOffset()}] = (byte)value;";
+ }
+ }
+
+ private static string GenerateSimpleIntegralAccessor(PacketFieldModel packetField, TypeConverterModel converter, bool isGetter)
+ {
+ if (isGetter)
+ {
+ string maybeCast = packetField.FieldType.IsEnum ? $"({packetField.FieldType.Name})" : string.Empty;
+
+ return $"return {maybeCast}{converter.GetReaderName(packetField.IsBigEndian)}(this.AsSpan({packetField.GetOffset()}, {converter.Length}));";
+ }
+ else
+ {
+ string valueParameter = packetField.FieldType.IsEnum ? $"({packetField.FieldType.ActualType.ToTypeString()})value" : "value";
+
+ return $"{converter.GetWriterName(packetField.IsBigEndian)}(this.AsSpan({packetField.GetOffset()}, {converter.Length}), {valueParameter});";
+ }
+ }
+
+ private static string[] GenerateSimpleStringAccessor(PacketFieldModel packetField, bool isGetter)
+ {
+ BlockBuilder source = new();
+
+ if (isGetter)
+ {
+ AddVerificationMethodIfNecessary(source, packetField, true);
+
+ source.AppendBlock(packetField.FieldStringEncoding.GetStringText($"this.AsSpan({packetField.GetOffset()}, {packetField.GetLength()})", packetField.IsBigEndian));
+ }
+ else
+ {
+ if (packetField.MinLength > 0)
+ {
+ ExceptionHelper.ArgumentOutOfRange.GenerateThrowIfLessThan(source, "value.Length", packetField.MinLength.ToString());
+ }
+ if (packetField.MaxLength > 0)
+ {
+ ExceptionHelper.ArgumentOutOfRange.GenerateThrowIfGreaterThan(source, "value.Length", packetField.MaxLength.ToString());
+ }
+
+ AddVerificationMethodIfNecessary(source, packetField, false);
+
+ if (packetField is { Length: <= 0, LengthMemberPermissions: Permissions.ReadWrite })
+ {
+ string maybeLengthMemberCast = packetField.LengthMemberType != ActualType.Int32 ? $"({packetField.LengthMemberType.ToTypeString()})" : string.Empty;
+
+ source.AppendLine($"this.{packetField.LengthMember} = {maybeLengthMemberCast}{packetField.FieldStringEncoding.GetByteCountText("value")};");
+ source.AppendBlock(packetField.FieldStringEncoding.GetBytesText("value", $"this.AsSpan({packetField.GetOffset()}, this.{packetField.LengthMember})", packetField.IsBigEndian));
+ }
+ else
+ {
+ if (packetField is { Length: <= 0, LengthMemberPermissions: Permissions.ReadOnly })
+ {
+ ExceptionHelper.ArgumentOutOfRange.GenerateThrowIfNotEqual(source, packetField.FieldStringEncoding.GetByteCountText("value"), packetField.GetLength(), "value", "byte length of value");
+ }
+
+ source.AppendBlock(packetField.FieldStringEncoding.GetBytesText("value", $"this.AsSpan({packetField.GetOffset()}, {packetField.GetLength()})", packetField.IsBigEndian));
+ }
+ }
+
+ return source.GetLines();
+ }
+
+ private static string[] GenerateSimpleAccessor(PacketFieldModel packetField, bool isGetter)
+ {
+ BlockBuilder source = new();
+
+ if (packetField.FieldType.ActualType != ActualType.NamedType)
+ {
+ AddVerificationMethodIfNecessary(source, packetField, isGetter);
+ }
+
+ switch (packetField.FieldType.ActualType)
+ {
+ case ActualType.Byte:
+ source.AppendLine(GenerateSimpleByteAccessor(packetField, isGetter));
+ break;
+ case ActualType.SByte:
+ source.AppendLine(GenerateSimpleSByteAccessor(packetField, isGetter));
+ break;
+ case ActualType.Int16:
+ case ActualType.UInt16:
+ case ActualType.Int32:
+ case ActualType.UInt32:
+ case ActualType.Int64:
+ case ActualType.UInt64:
+ var converter = TypeConverter.Map[packetField.FieldType.ActualType];
+ source.AppendLine(GenerateSimpleIntegralAccessor(packetField, converter, isGetter));
+ break;
+ case ActualType.NamedType:
+ // Only deal with strings here
+ source.AppendBlock(GenerateSimpleStringAccessor(packetField, isGetter));
+ break;
+ default:
+ throw new InvalidOperationException($"Unable to generate simple accessor for type: {packetField.FieldType.Name}({packetField.FieldType.ActualType})");
+ }
+
+ return source.GetLines();
+ }
+ }
+}
diff --git a/RyuSocks.Generator/Packet/AccessorGenerator.cs b/RyuSocks.Generator/Packet/AccessorGenerator.cs
new file mode 100644
index 0000000..75149ff
--- /dev/null
+++ b/RyuSocks.Generator/Packet/AccessorGenerator.cs
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) RyuSOCKS
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using RyuSocks.Generator.Builder;
+using System;
+
+namespace RyuSocks.Generator.Packet
+{
+ internal static partial class AccessorGenerator
+ {
+ private static void AddVerificationMethodIfNecessary(BlockBuilder source, PacketFieldModel packetField, bool isGetter)
+ {
+ if (packetField.ValidationMethodName.Length > 0)
+ {
+ string maybeValueParam = isGetter ? string.Empty : "value";
+
+ source.AppendLine($"this.{packetField.ValidationMethodName}({maybeValueParam});");
+ }
+ }
+
+ private static string[] Generate(PacketFieldModel packetField, bool isGetter)
+ {
+ BlockBuilder source = new();
+ source.EnterScope(isGetter ? $"{packetField.PropertyGetterModifiers}get" : $"{packetField.PropertySetterModifiers}set");
+
+ if (packetField.FieldType.IsArray)
+ {
+ // TODO: Deal with arrays
+ // Arrays could contain structs, enums or classes
+ source.AppendBlock(GenerateArrayAccessor(packetField, isGetter));
+ }
+ else if (packetField.FieldType.IsStruct)
+ {
+ // TODO: Deal with structs
+ source.AppendLine("// TODO: structs");
+ }
+ // Deal with simple types: enums, strings and integral numeric types
+ else if (packetField.FieldType is { ActualType: ActualType.NamedType, Name: "string" }
+ || packetField.FieldType.ActualType != ActualType.NamedType)
+ {
+ source.AppendBlock(GenerateSimpleAccessor(packetField, isGetter));
+ }
+ else if (packetField.FieldType.ActualType == ActualType.NamedType)
+ {
+ source.AppendBlock(GenerateClassAccessor(packetField, isGetter));
+ }
+ else
+ {
+ throw new InvalidOperationException($"Unknown property type. Unable to generate accessor for: {packetField.FieldType.Name}({packetField.FieldType.ActualType})");
+ }
+
+ source.LeaveScope();
+ return source.GetLines();
+ }
+
+ public static string[] GenerateGetter(PacketFieldModel packetField) => Generate(packetField, true);
+
+ public static string[] GenerateSetter(PacketFieldModel packetField) => Generate(packetField, false);
+ }
+}
diff --git a/RyuSocks.Generator/Packet/ActualType.cs b/RyuSocks.Generator/Packet/ActualType.cs
new file mode 100644
index 0000000..0ab71c4
--- /dev/null
+++ b/RyuSocks.Generator/Packet/ActualType.cs
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) RyuSOCKS
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+namespace RyuSocks.Generator.Packet
+{
+ internal enum ActualType
+ {
+ // class or struct
+ NamedType,
+ SByte,
+ Byte,
+ Int16,
+ UInt16,
+ Int32,
+ UInt32,
+ Int64,
+ UInt64,
+ }
+}
diff --git a/RyuSocks.Generator/Packet/ActualTypeExtensions.cs b/RyuSocks.Generator/Packet/ActualTypeExtensions.cs
new file mode 100644
index 0000000..0d08be1
--- /dev/null
+++ b/RyuSocks.Generator/Packet/ActualTypeExtensions.cs
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) RyuSOCKS
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using System;
+
+namespace RyuSocks.Generator.Packet
+{
+ internal static class ActualTypeExtensions
+ {
+ public static string ToTypeString(this ActualType actualType)
+ {
+ return actualType switch
+ {
+ ActualType.SByte => "sbyte",
+ ActualType.Byte => "byte",
+ ActualType.Int16 => "short",
+ ActualType.UInt16 => "ushort",
+ ActualType.Int32 => "int",
+ ActualType.UInt32 => "uint",
+ ActualType.Int64 => "long",
+ ActualType.UInt64 => "ulong",
+ _ => throw new InvalidOperationException($"Couldn't get type string from {nameof(ActualType)}: {actualType}"),
+ };
+ }
+
+ public static ActualType ToActualType(this string typeString)
+ {
+ return typeString.Trim().ToLowerInvariant() switch
+ {
+ "sbyte" => ActualType.SByte,
+ "byte" => ActualType.Byte,
+ "short" => ActualType.Int16,
+ "ushort" => ActualType.UInt16,
+ "int" => ActualType.Int32,
+ "uint" => ActualType.UInt32,
+ "long" => ActualType.Int64,
+ "ulong" => ActualType.UInt64,
+ _ => (ActualType)Enum.Parse(typeof(ActualType), typeString, true),
+ };
+ }
+ }
+}
diff --git a/RyuSocks.Generator/Packet/ExceptionHelper.cs b/RyuSocks.Generator/Packet/ExceptionHelper.cs
new file mode 100644
index 0000000..8a98c8e
--- /dev/null
+++ b/RyuSocks.Generator/Packet/ExceptionHelper.cs
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) RyuSOCKS
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using RyuSocks.Generator.Builder;
+using System.Runtime.CompilerServices;
+
+namespace RyuSocks.Generator.Packet
+{
+ // NOTE: Required for compatability with .NET Standard and .NET < 8
+ internal static class ExceptionHelper
+ {
+ public static class ArgumentOutOfRange
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void GenerateException(BlockBuilder source, string value, string rationalOperator, string other, string operatorMessageText, string overrideValueName, string overrideValueText)
+ {
+ string nameofText = overrideValueName.Length > 0 ? overrideValueName : value;
+ string valueText = overrideValueText.Length > 0 ? overrideValueText : $"{{nameof({value})}}";
+ string message = $"$\"{valueText} must be {operatorMessageText} to: {{{other}}}\"";
+ source.EnterScope($"if ({value} {rationalOperator} {other})");
+ source.AppendLine($"throw new ArgumentOutOfRangeException(nameof({nameofText}), {value}, {message});");
+ source.LeaveScope();
+ }
+
+ public static void GenerateThrowIfNotEqual(BlockBuilder source, string value, string other, string overrideValueName = "", string overrideValueText = "")
+ {
+ GenerateException(source, value, "!=", other, "equal", overrideValueName, overrideValueText);
+ }
+
+ public static void GenerateThrowIfLessThan(BlockBuilder source, string value, string other, string overrideValueName = "", string overrideValueText = "")
+ {
+ GenerateException(source, value, "<", other, "larger or equal", overrideValueName, overrideValueText);
+ }
+
+ public static void GenerateThrowIfGreaterThan(BlockBuilder source, string value, string other, string overrideValueName = "", string overrideValueText = "")
+ {
+ GenerateException(source, value, ">", other, "smaller or equal", overrideValueName, overrideValueText);
+ }
+ }
+ }
+}
diff --git a/RyuSocks.Generator/Packet/ModelExtensions.cs b/RyuSocks.Generator/Packet/ModelExtensions.cs
new file mode 100644
index 0000000..4759e8a
--- /dev/null
+++ b/RyuSocks.Generator/Packet/ModelExtensions.cs
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) RyuSOCKS
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using System;
+
+namespace RyuSocks.Generator.Packet
+{
+ internal static class ModelExtensions
+ {
+ public static string GetOffset(this PacketFieldModel model)
+ {
+ if (model.Offset > -1)
+ {
+ return model.Offset.ToString();
+ }
+
+ if (model.OffsetMember.Length == 0)
+ {
+ throw new InvalidOperationException($"No {nameof(model.OffsetMember)} found.");
+ }
+
+ return $"this.{model.OffsetMember}";
+ }
+
+ public static string GetLength(this PacketFieldModel model)
+ {
+ if (model.Length > -1)
+ {
+ return model.Length.ToString();
+ }
+
+ if (model.LengthMember.Length == 0)
+ {
+ throw new InvalidOperationException($"No {nameof(model.LengthMember)} found.");
+ }
+
+ return $"this.{model.LengthMember}";
+ }
+ }
+}
diff --git a/RyuSocks.Generator/Packet/Models.cs b/RyuSocks.Generator/Packet/Models.cs
new file mode 100644
index 0000000..dc565d7
--- /dev/null
+++ b/RyuSocks.Generator/Packet/Models.cs
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) RyuSOCKS
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+namespace RyuSocks.Generator.Packet
+{
+ internal record struct FieldTypeModel(
+ string Name,
+ bool IsArray,
+ bool IsEnum,
+ bool IsStruct,
+ ActualType ActualType
+ );
+
+ internal record struct PacketFieldModel(
+ int Offset,
+ string OffsetMember,
+ int Length,
+ string LengthMember,
+ ActualType LengthMemberType,
+ Permissions LengthMemberPermissions,
+ int MinLength,
+ int MaxLength,
+ bool IsBigEndian,
+ FieldTypeModel FieldType,
+ StringEncoding FieldStringEncoding,
+ string PropertyName,
+ string PropertyAccessModifiers,
+ string PropertyGetterModifiers,
+ string PropertySetterModifiers,
+ string ClassName,
+ string[] Imports,
+ string ValidationMethodName
+ );
+}
diff --git a/RyuSocks.Generator/Packet/PacketFieldAttributeData.cs b/RyuSocks.Generator/Packet/PacketFieldAttributeData.cs
new file mode 100644
index 0000000..66cf71d
--- /dev/null
+++ b/RyuSocks.Generator/Packet/PacketFieldAttributeData.cs
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) RyuSOCKS
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using Microsoft.CodeAnalysis;
+using System;
+using System.Linq;
+
+namespace RyuSocks.Generator.Packet
+{
+ // NOTE: The default values should be the same as the ones specified in PacketFieldAttribute.
+ // See: PacketGenerator.cs
+ internal class PacketFieldAttributeData
+ {
+ public int Offset { get; } = -1;
+ public string OffsetMember { get; } = string.Empty;
+ public int Length { get; }
+ public string LengthMember { get; }
+ public bool IsBigEndian { get; }
+ public int MinLength { get; }
+ public int MaxLength { get; }
+ public StringEncoding StringEncoding { get; }
+ public string AssumeGeneratedEnumType { get; }
+ public string ValidationMethod { get; }
+
+ public PacketFieldAttributeData(AttributeData attributeData)
+ {
+ if (attributeData.ConstructorArguments.Length == 0)
+ {
+ throw new InvalidOperationException($"Attribute constructor doesn't have arguments: {attributeData.AttributeClass?.Name}");
+ }
+
+ // Parse constructor arguments
+ switch (attributeData.ConstructorArguments[0].Type!.Name)
+ {
+ case nameof(String):
+ OffsetMember = (string)attributeData.ConstructorArguments[0].Value!;
+ break;
+ case nameof(Int32):
+ Offset = (int)attributeData.ConstructorArguments[0].Value!;
+ break;
+ default:
+ throw new InvalidOperationException($"Attribute constructor argument type is not valid: {attributeData.ConstructorArguments[0].Type!.Name}");
+ }
+
+ // Parse named arguments
+ TypedConstant lengthArg = attributeData.NamedArguments.SingleOrDefault(kvp => kvp.Key == nameof(Length)).Value;
+ TypedConstant lengthMemberArg = attributeData.NamedArguments.SingleOrDefault(kvp => kvp.Key == nameof(LengthMember)).Value;
+ TypedConstant isBigEndianArg = attributeData.NamedArguments.SingleOrDefault(kvp => kvp.Key == nameof(IsBigEndian)).Value;
+ TypedConstant minLengthArg = attributeData.NamedArguments.SingleOrDefault(kvp => kvp.Key == nameof(MinLength)).Value;
+ TypedConstant maxLengthArg = attributeData.NamedArguments.SingleOrDefault(kvp => kvp.Key == nameof(MaxLength)).Value;
+ TypedConstant stringEncodingArg = attributeData.NamedArguments.SingleOrDefault(kvp => kvp.Key == nameof(StringEncoding)).Value;
+ TypedConstant assumeGeneratedEnumTypeArg = attributeData.NamedArguments.SingleOrDefault(kvp => kvp.Key == nameof(AssumeGeneratedEnumType)).Value;
+ TypedConstant validationMethodArg = attributeData.NamedArguments.SingleOrDefault(kvp => kvp.Key == nameof(ValidationMethod)).Value;
+
+ // Get the actual value of every named argument which was specified
+ Length = !lengthArg.IsNull ? (int)lengthArg.Value! : -1;
+ LengthMember = !lengthMemberArg.IsNull ? (string)lengthMemberArg.Value! : string.Empty;
+ IsBigEndian = !isBigEndianArg.IsNull && (bool)isBigEndianArg.Value!;
+ MinLength = !minLengthArg.IsNull ? (int)minLengthArg.Value! : -1;
+ MaxLength = !maxLengthArg.IsNull ? (int)maxLengthArg.Value! : -1;
+ StringEncoding = !stringEncodingArg.IsNull
+ ? (StringEncoding)stringEncodingArg.Value!
+ : StringEncoding.ASCII;
+ AssumeGeneratedEnumType = !assumeGeneratedEnumTypeArg.IsNull
+ ? (string)assumeGeneratedEnumTypeArg.Value!
+ : string.Empty;
+ ValidationMethod = !validationMethodArg.IsNull
+ ? (string)validationMethodArg.Value!
+ : string.Empty;
+ }
+ }
+}
diff --git a/RyuSocks.Generator/Packet/Permissions.cs b/RyuSocks.Generator/Packet/Permissions.cs
new file mode 100644
index 0000000..2aa4633
--- /dev/null
+++ b/RyuSocks.Generator/Packet/Permissions.cs
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) RyuSOCKS
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+namespace RyuSocks.Generator.Packet
+{
+ internal enum Permissions
+ {
+ Unknown,
+ ReadOnly,
+ WriteOnly,
+ ReadWrite,
+ }
+}
diff --git a/RyuSocks.Generator/Packet/StringEncoding.cs b/RyuSocks.Generator/Packet/StringEncoding.cs
new file mode 100644
index 0000000..7ffc973
--- /dev/null
+++ b/RyuSocks.Generator/Packet/StringEncoding.cs
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) RyuSOCKS
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+namespace RyuSocks.Generator.Packet
+{
+ // NOTE: Keep this in sync with the generated string encoding enum in PacketGenerator.cs
+ internal enum StringEncoding : byte
+ {
+ ASCII,
+ Unicode,
+ UTF7,
+ UTF8,
+ UTF32,
+ }
+}
diff --git a/RyuSocks.Generator/Packet/StringEncodingExtensions.cs b/RyuSocks.Generator/Packet/StringEncodingExtensions.cs
new file mode 100644
index 0000000..a8e5755
--- /dev/null
+++ b/RyuSocks.Generator/Packet/StringEncodingExtensions.cs
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) RyuSOCKS
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using RyuSocks.Generator.Builder;
+
+namespace RyuSocks.Generator.Packet
+{
+ internal static class StringEncodingExtensions
+ {
+ public static string GetByteCountText(this StringEncoding stringEncoding, string sourceArgText)
+ {
+ return $"Encoding.{stringEncoding}.GetByteCount({sourceArgText})";
+ }
+
+ public static string[] GetBytesText(this StringEncoding stringEncoding, string sourceArgText, string destinationArgText, bool isBigEndian)
+ {
+ if (isBigEndian)
+ {
+ switch (stringEncoding)
+ {
+ case StringEncoding.Unicode:
+ return [$"Encoding.BigEndianUnicode.GetBytes({sourceArgText}, {destinationArgText});"];
+ case StringEncoding.UTF32:
+ BlockBuilder source = new();
+
+ // Write bytes in little endian to temp array
+ source.AppendLine($"byte[] byteArray = Encoding.UTF32.GetBytes({sourceArgText});");
+ source.AppendLine("Array.Reverse(byteArray);");
+ // Write bytes with correct endianness to the packet
+ source.AppendLine($"byteArray.CopyTo({destinationArgText});");
+
+ return source.GetLines();
+ }
+ }
+
+ return [$"Encoding.{stringEncoding}.GetBytes({sourceArgText}, {destinationArgText});"];
+ }
+
+ public static string[] GetStringText(this StringEncoding stringEncoding, string sourceArgText, bool isBigEndian, bool returnValue = true)
+ {
+ string returnOrAssignText = returnValue ? "return" : "string valueString =";
+
+ if (isBigEndian)
+ {
+ switch (stringEncoding)
+ {
+ case StringEncoding.Unicode:
+ return [$"{returnOrAssignText} Encoding.BigEndianUnicode.GetString({sourceArgText});"];
+ case StringEncoding.UTF32:
+ BlockBuilder source = new();
+
+ // Get a temp array, so the original packet doesn't get modified.
+ source.AppendLine($"byte[] stringArray = {sourceArgText}.ToArray();");
+ source.AppendLine("Array.Reverse(stringArray);");
+ source.AppendLine($"{returnOrAssignText} Encoding.UTF32.GetString(stringArray);");
+
+ return source.GetLines();
+ }
+ }
+
+ return [$"{returnOrAssignText} Encoding.{stringEncoding}.GetString({sourceArgText});"];
+ }
+ }
+}
diff --git a/RyuSocks.Generator/Packet/TypeConverter.cs b/RyuSocks.Generator/Packet/TypeConverter.cs
new file mode 100644
index 0000000..74d9e5f
--- /dev/null
+++ b/RyuSocks.Generator/Packet/TypeConverter.cs
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) RyuSOCKS
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using System.Collections.Generic;
+using System.Collections.Immutable;
+
+namespace RyuSocks.Generator.Packet
+{
+ internal static class TypeConverter
+ {
+ public static readonly ImmutableDictionary Map = new Dictionary
+ {
+ { ActualType.Int16, new TypeConverterModel(sizeof(short), "BinaryPrimitives.ReadInt16", "BinaryPrimitives.WriteInt16") },
+ { ActualType.UInt16, new TypeConverterModel(sizeof(ushort), "BinaryPrimitives.ReadUInt16", "BinaryPrimitives.WriteUInt16") },
+ { ActualType.Int32, new TypeConverterModel(sizeof(int), "BinaryPrimitives.ReadInt32", "BinaryPrimitives.WriteInt32") },
+ { ActualType.UInt32, new TypeConverterModel(sizeof(uint), "BinaryPrimitives.ReadUInt32", "BinaryPrimitives.WriteUInt32") },
+ { ActualType.Int64, new TypeConverterModel(sizeof(long), "BinaryPrimitives.ReadInt64", "BinaryPrimitives.WriteInt64") },
+ { ActualType.UInt64, new TypeConverterModel(sizeof(ulong), "BinaryPrimitives.ReadUInt64", "BinaryPrimitives.WriteUInt64") },
+ }.ToImmutableDictionary();
+ }
+
+ internal readonly record struct TypeConverterModel
+ {
+ public int Length { get; }
+ private string ReaderMethodName { get; }
+ private string WriterMethodName { get; }
+
+ public TypeConverterModel(int length, string readerMethodName, string writerMethodName)
+ {
+ Length = length;
+ ReaderMethodName = readerMethodName;
+ WriterMethodName = writerMethodName;
+ }
+
+ public string GetReaderName(bool isBigEndian)
+ {
+ return isBigEndian ? $"{ReaderMethodName}BigEndian" : $"{ReaderMethodName}LittleEndian";
+ }
+
+ public string GetWriterName(bool isBigEndian)
+ {
+ return isBigEndian ? $"{WriterMethodName}BigEndian" : $"{WriterMethodName}LittleEndian";
+ }
+ }
+}
diff --git a/RyuSocks.Generator/PacketGenerator.cs b/RyuSocks.Generator/PacketGenerator.cs
new file mode 100644
index 0000000..43920e2
--- /dev/null
+++ b/RyuSocks.Generator/PacketGenerator.cs
@@ -0,0 +1,511 @@
+/*
+ * Copyright (C) RyuSOCKS
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using RyuSocks.Generator.Builder;
+using RyuSocks.Generator.Packet;
+using System;
+using System.Linq;
+using System.Threading;
+
+namespace RyuSocks.Generator
+{
+ // TODO: Write an analyzer for PacketFieldAttribute
+ // - make sure (Offset and OffsetMember) and (Length and LengthMember) aren't used at the same time
+ // - check whether the attribute is used on a property within a class/subclass that extends Packet
+
+ [Generator]
+ public class PacketGenerator : IIncrementalGenerator
+ {
+ private const string Namespace = "RyuSocks.Packets";
+ private const string AbstractClassName = "Packet";
+ private const string PacketFieldAttributeName = "PacketFieldAttribute";
+ private const string StringEncodingEnumName = "StringEncoding";
+
+ #region SourceText
+ private const string EncodingEnumText = @"
+namespace %NAMESPACE%
+{
+ public enum %ENUM_NAME% : byte
+ {
+ ASCII,
+ Unicode,
+ UTF7,
+ UTF8,
+ UTF32,
+ }
+}
+";
+
+ private const string PacketFieldAttributeText = @"
+using System;
+
+namespace %NAMESPACE%
+{
+ [AttributeUsage(AttributeTargets.Property, Inherited = false)]
+ [System.Diagnostics.Conditional(""RyuSocks_PacketGenerator_DEBUG"")]
+ public sealed class %ATTRIBUTE_NAME% : Attribute
+ {
+ private int offset = -1;
+ private string offsetMember = string.Empty;
+
+ ///
+ /// Marks this property as a field of the packet.
+ /// The containing class needs to extend .
+ ///
+ /// The offset of this field in the packet.
+ public %ATTRIBUTE_NAME%([System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] int offset)
+ {
+ this.offset = offset;
+ }
+
+ ///
+ /// Marks this property as a field of the packet.
+ /// The containing class needs to extend .
+ ///
+ /// The name of the member which specifies the offset of this field in the packet.
+ public %ATTRIBUTE_NAME%(string offsetMember)
+ {
+ this.offsetMember = offsetMember;
+ }
+
+ ///
+ /// The length of this field in bytes.
+ /// Only required if it can't be determined from the property type.
+ ///
+ public int Length { get; set; } = -1;
+
+ ///
+ /// The name of the member which specifies the length of this field in bytes.
+ /// Only required if it can't be determined from the property type.
+ ///
+ public string LengthMember { get; set; } = string.Empty;
+
+ ///
+ /// Whether this field is big endian.
+ ///
+ public bool IsBigEndian { get; set; } = false;
+
+ ///
+ /// The minimum length of the array or chars allowed in this field.
+ ///
+ public int MinLength { get; set; } = -1;
+
+ ///
+ /// The maximum length of the array or chars allowed in this field.
+ ///
+ public int MaxLength { get; set; } = -1;
+
+ ///
+ /// The encoding of the underlying string.
+ /// Only required for string properties.
+ ///
+ public %STRING_ENCODING_ENUM_NAME% StringEncoding = %STRING_ENCODING_ENUM_NAME%.ASCII;
+
+ ///
+ /// The name of the underlying type of the property type.
+ /// Only required if the type of the property is source generated.
+ ///
+ public string AssumeGeneratedEnumType { get; set; } = string.Empty;
+
+ ///
+ /// The name of the method which should be invoked before the getter/setter is executed.
+ ///
+ ///
+ /// The method type must be void and have one optional parameter with the same type as the property.
+ /// If verification fails an exception should be thrown.
+ ///
+ public string ValidationMethod { get; set; } = string.Empty;
+
+ public int Offset => offset;
+ public string OffsetMember => offsetMember;
+ }
+}
+";
+
+ private const string AbstractPacketClassText = @"
+using System;
+
+namespace %NAMESPACE%
+{
+ public abstract partial class %CLASS_NAME%
+ {
+ ///
+ /// The contents of the packet.
+ ///
+ public byte[] Bytes { get; protected set; }
+
+ ///
+ public byte this[int i]
+ {
+ get => Bytes[i];
+ set => Bytes[i] = value;
+ }
+
+ ///
+ /// Creates a new span over the packet.
+ ///
+ ///
+ ///
+ public Span AsSpan() => Bytes;
+
+ ///
+ /// Creates a new Span over the portion of the packet beginning
+ /// at 'start' index and ending at 'end' index (exclusive).
+ ///
+ /// The index at which to begin the Span.
+ /// The number of items in the Span.
+ ///
+ public Span AsSpan(int start, int length) => Bytes.AsSpan(start, length);
+
+ protected %CLASS_NAME%() { }
+
+ protected %CLASS_NAME%(byte[] bytes)
+ {
+ Bytes = bytes;
+ }
+ }
+}
+";
+ #endregion
+
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ // Register the string encoding enum source
+ context.RegisterPostInitializationOutput(static postContext => postContext.AddSource(
+ $"{StringEncodingEnumName}.g.cs",
+ EncodingEnumText
+ .Replace("%NAMESPACE%", Namespace)
+ .Replace("%ENUM_NAME%", StringEncodingEnumName)
+ .TrimStart()
+ )
+ );
+ // Register the attribute source
+ context.RegisterPostInitializationOutput(static postContext => postContext.AddSource(
+ $"{PacketFieldAttributeName}.g.cs",
+ PacketFieldAttributeText
+ .Replace("%NAMESPACE%", Namespace)
+ .Replace("%ATTRIBUTE_NAME%", PacketFieldAttributeName)
+ .Replace("%ABSTRACT_CLASS_NAME%", AbstractClassName)
+ .Replace("%STRING_ENCODING_ENUM_NAME%", StringEncodingEnumName)
+ .TrimStart()
+ )
+ );
+ // Register the abstract packet class source
+ context.RegisterPostInitializationOutput(static postContext => postContext.AddSource(
+ $"{AbstractClassName}.g.cs",
+ AbstractPacketClassText
+ .Replace("%NAMESPACE%", Namespace)
+ .Replace("%CLASS_NAME%", AbstractClassName)
+ .TrimStart()
+ )
+ );
+
+ // Create an incremental value provider using the generated attribute
+ var packetFieldProvider = context.SyntaxProvider.ForAttributeWithMetadataName(
+ $"{Namespace}.{PacketFieldAttributeName}",
+ IsValidPacketFieldProperty,
+ TransformPacketFieldProperty
+ );
+
+ context.RegisterImplementationSourceOutput(packetFieldProvider, ProduceSourceCode);
+ }
+
+ ///
+ /// Check whether is a valid packet field property.
+ ///
+ /// The syntax node to check.
+ /// The cancellation token.
+ /// True if is a valid packet field property.
+ private static bool IsValidPacketFieldProperty(SyntaxNode syntaxNode, CancellationToken cancellationToken) =>
+ syntaxNode is PropertyDeclarationSyntax { Parent: ClassDeclarationSyntax { BaseList.Types.Count: > 0 } classDeclaration } propertyDeclaration
+ && propertyDeclaration.Modifiers.Any(modifier => modifier.IsKind(SyntaxKind.PartialKeyword))
+ && classDeclaration.Modifiers.Any(modifier => modifier.IsKind(SyntaxKind.PartialKeyword));
+
+ private static PacketFieldModel TransformPacketFieldProperty(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken)
+ {
+ var propertySymbol = (context.TargetSymbol as IPropertySymbol)!;
+ var attributeData = new PacketFieldAttributeData(context.Attributes[0]);
+ var classSymbol = (context.TargetSymbol.ContainingSymbol as INamedTypeSymbol)!;
+
+ // Get information about the type of the property
+
+ ITypeSymbol actualTypeSymbol = propertySymbol.Type;
+ bool isArray = propertySymbol.Type.TypeKind == TypeKind.Array;
+ bool isEnum = propertySymbol.Type.TypeKind == TypeKind.Enum;
+ bool isStruct = IsActualStruct(propertySymbol.Type);
+ if (!Enum.TryParse(actualTypeSymbol.Name, out ActualType actualType))
+ {
+ actualType = ActualType.NamedType;
+ }
+
+ if (isArray)
+ {
+ // Get the type of the array
+ actualTypeSymbol = (propertySymbol.Type as IArrayTypeSymbol)!.ElementType;
+ isEnum = actualTypeSymbol.TypeKind == TypeKind.Enum;
+ isStruct = IsActualStruct(actualTypeSymbol);
+
+ // Parse the ActualType value if the array type is neither an enum, a struct, a class or a generated type
+ if (!isEnum && !isStruct && actualTypeSymbol.TypeKind != TypeKind.Class && actualTypeSymbol.TypeKind != TypeKind.Error)
+ {
+ actualType = (ActualType)Enum.Parse(typeof(ActualType), actualTypeSymbol.Name, true);
+ }
+ }
+
+ if (isEnum)
+ {
+ // Parse the underlying type for enums
+ actualType = (ActualType)Enum.Parse(typeof(ActualType), (actualTypeSymbol as INamedTypeSymbol)!.EnumUnderlyingType!.Name, true);
+ }
+
+ // Get information about length member if applicable
+ ActualType lengthMemberType = ActualType.NamedType;
+ Permissions lengthMemberPermissions = Permissions.Unknown;
+ if (attributeData.LengthMember.Length > 0)
+ {
+ ISymbol lengthMemberSymbol = classSymbol.GetMembers(attributeData.LengthMember).Single();
+
+ switch (lengthMemberSymbol)
+ {
+ case IPropertySymbol lengthProperty:
+ lengthMemberType = (ActualType)Enum.Parse(typeof(ActualType), lengthProperty.Type.Name, true);
+ if (lengthProperty.IsReadOnly)
+ {
+ lengthMemberPermissions = Permissions.ReadOnly;
+ }
+ else if (lengthProperty.IsWriteOnly)
+ {
+ lengthMemberPermissions = Permissions.WriteOnly;
+ }
+ else
+ {
+ lengthMemberPermissions = Permissions.ReadWrite;
+ }
+ break;
+ case IFieldSymbol lengthField:
+ lengthMemberType = (ActualType)Enum.Parse(typeof(ActualType), lengthField.Type.Name, true);
+ if (lengthField.IsReadOnly || lengthField.IsConst)
+ {
+ lengthMemberPermissions = Permissions.ReadOnly;
+ }
+ else
+ {
+ lengthMemberPermissions = Permissions.ReadWrite;
+ }
+ break;
+ default:
+ throw new InvalidOperationException(
+ $"Can't determine member type of LengthMember: {lengthMemberSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}");
+ }
+
+ if (lengthMemberPermissions == Permissions.WriteOnly)
+ {
+ throw new InvalidOperationException("Can't generate property accessors, LengthMember is write-only.");
+ }
+ }
+
+ // NOTE: This is only necessary because property types could be generated by other source generators,
+ // which aren't available at this point.
+
+ // Process auto generated property types
+
+ // Assume generated enum type
+ if (actualTypeSymbol.TypeKind == TypeKind.Error && attributeData.AssumeGeneratedEnumType.Length > 0)
+ {
+ isEnum = true;
+ actualType = attributeData.AssumeGeneratedEnumType.ToActualType();
+ }
+
+ // Get imports
+
+ CompilationUnitSyntax rootNode = context.TargetNode.SyntaxTree.GetCompilationUnitRoot();
+ string[] imports = rootNode.Usings.ToString().Split('\n');
+ for (int i = 0; i < imports.Length; i++)
+ {
+ imports[i] = imports[i].Trim();
+ }
+
+ // Create model from data
+ return new PacketFieldModel(
+ attributeData.Offset,
+ attributeData.OffsetMember,
+ attributeData.Length,
+ attributeData.LengthMember,
+ lengthMemberType,
+ lengthMemberPermissions,
+ attributeData.MinLength,
+ attributeData.MaxLength,
+ attributeData.IsBigEndian,
+ new FieldTypeModel(
+ RemoveGlobalAlias(propertySymbol.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)),
+ isArray,
+ isEnum,
+ isStruct,
+ actualType
+ ),
+ attributeData.StringEncoding,
+ propertySymbol.Name,
+ GetAccessModifierString(propertySymbol),
+ GetAccessorModifierString(propertySymbol, true),
+ GetAccessorModifierString(propertySymbol, false),
+ RemoveGlobalAlias(classSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)),
+ imports,
+ attributeData.ValidationMethod
+ );
+ }
+
+ private static void ProduceSourceCode(SourceProductionContext context, PacketFieldModel packetField)
+ {
+ string namespaceName = string.Empty;
+ string className = packetField.ClassName;
+
+ if (className.Contains("."))
+ {
+ namespaceName = className.Substring(0, className.LastIndexOf(".", StringComparison.Ordinal));
+ className = className.Substring(className.LastIndexOf(".", StringComparison.Ordinal) + 1);
+ }
+
+ // Begin building the generated source
+ CodeBuilder source = new();
+ string[] requiredImports = ["using System;", "using System.Buffers.Binary;", "using System.Text;"];
+
+ // Add imports
+
+ foreach (var importString in packetField.Imports)
+ {
+ source.AppendLine(importString);
+ }
+
+ foreach (var requiredImportString in requiredImports)
+ {
+ if (!packetField.Imports.Contains(requiredImportString))
+ {
+ source.AppendLine(requiredImportString);
+ }
+ }
+
+ source.AppendLine();
+
+ // Add namespace if necessary
+
+ if (namespaceName.Length > 0)
+ {
+ source.EnterScope($"namespace {namespaceName}");
+ }
+
+ // Add class and property
+
+ source.EnterScope($"partial class {className}");
+ source.EnterScope($"{packetField.PropertyAccessModifiers}partial {packetField.FieldType.Name} {packetField.PropertyName}");
+
+ // Add code for getter
+ source.AppendBlock(AccessorGenerator.GenerateGetter(packetField));
+ // Add code for setter
+ source.AppendBlock(AccessorGenerator.GenerateSetter(packetField));
+
+ // Leave property and class scope
+ source.LeaveScope();
+ source.LeaveScope();
+
+ // Leave namespace scope if necessary
+ if (namespaceName.Length > 0)
+ {
+ source.LeaveScope();
+ }
+
+ // Add the generated source
+ string fullyQualifiedName = namespaceName.Length > 0 ? $"{namespaceName}.{className}" : className;
+ context.AddSource($"{fullyQualifiedName}.{packetField.PropertyName}.g.cs", source.ToString());
+ }
+
+ private static string RemoveGlobalAlias(string classOrNamespaceName) =>
+ classOrNamespaceName.StartsWith("global::") ? classOrNamespaceName.Substring(8) : classOrNamespaceName;
+
+ private static bool IsActualStruct(ITypeSymbol typeSymbol)
+ {
+ if (typeSymbol.TypeKind != TypeKind.Struct)
+ {
+ return false;
+ }
+
+ return typeSymbol.SpecialType is < SpecialType.System_Boolean or > SpecialType.System_String;
+ }
+
+ private static string GetAccessModifierString(IPropertySymbol propertySymbol)
+ {
+ if (propertySymbol.DeclaringSyntaxReferences.Length != 1)
+ {
+ throw new InvalidOperationException($"PropertySymbol contains wrong amount of declaring syntax references: {propertySymbol.DeclaringSyntaxReferences.Length}");
+ }
+
+ if (propertySymbol.DeclaringSyntaxReferences[0].GetSyntax() is not PropertyDeclarationSyntax propertyDeclarationSyntax)
+ {
+ throw new InvalidOperationException($"DeclaringSyntaxReference is not {nameof(PropertyDeclarationSyntax)}: {propertySymbol.DeclaringSyntaxReferences[0].GetSyntax().Kind()}");
+ }
+
+ string result = "";
+
+ foreach (var modifier in propertyDeclarationSyntax.Modifiers)
+ {
+ if (modifier.IsKind(SyntaxKind.PartialKeyword))
+ {
+ continue;
+ }
+
+ result += $"{modifier} ";
+ }
+
+ // NOTE: Keep the last space to make working with it easier.
+ return result;
+ }
+
+ private static string GetAccessorModifierString(IPropertySymbol propertySymbol, bool isGetter)
+ {
+ if (propertySymbol.DeclaringSyntaxReferences.Length != 1)
+ {
+ throw new InvalidOperationException($"PropertySymbol contains wrong amount of declaring syntax references: {propertySymbol.DeclaringSyntaxReferences.Length}");
+ }
+
+ if (propertySymbol.DeclaringSyntaxReferences[0].GetSyntax() is not PropertyDeclarationSyntax propertyDeclarationSyntax)
+ {
+ throw new InvalidOperationException($"DeclaringSyntaxReference is not {nameof(PropertyDeclarationSyntax)}: {propertySymbol.DeclaringSyntaxReferences[0].GetSyntax().Kind()}");
+ }
+
+ SyntaxKind accessorKind = isGetter ? SyntaxKind.GetAccessorDeclaration : SyntaxKind.SetAccessorDeclaration;
+
+ foreach (var accessor in propertyDeclarationSyntax.AccessorList!.Accessors)
+ {
+ if (!accessor.IsKind(accessorKind))
+ {
+ continue;
+ }
+
+ if (accessor.Modifiers.Count > 0)
+ {
+ // NOTE: Add a space to the end to make working with it easier.
+ return $"{accessor.Modifiers} ";
+ }
+
+ return string.Empty;
+ }
+
+ throw new InvalidOperationException($"Couldn't find property accessor for: {propertySymbol.Name}");
+ }
+ }
+}
diff --git a/RyuSocks.Test/Auth/AuthMethodExtensionsTests.cs b/RyuSocks.Test/Auth/AuthMethodExtensionsTests.cs
index 6e07d1e..3adddef 100644
--- a/RyuSocks.Test/Auth/AuthMethodExtensionsTests.cs
+++ b/RyuSocks.Test/Auth/AuthMethodExtensionsTests.cs
@@ -19,11 +19,13 @@
using RyuSocks.Test.Utils;
using RyuSocks.Types;
using System;
+using System.Diagnostics.CodeAnalysis;
using Xunit;
namespace RyuSocks.Test.Auth
{
+ [ExcludeFromCodeCoverage]
public class AuthMethodExtensionsTests
{
public static readonly TheoryData AuthImplObjects = new()
@@ -96,6 +98,7 @@ public void GetAuth_ThrowsOnUnknownImpl()
}
}
+ [ExcludeFromCodeCoverage]
internal class UnknownAuth : IProxyAuth
{
public int WrapperLength => throw new NotImplementedException();
diff --git a/RyuSocks.Test/Auth/NoAuthTests.cs b/RyuSocks.Test/Auth/NoAuthTests.cs
index e9a31b6..754e0e8 100644
--- a/RyuSocks.Test/Auth/NoAuthTests.cs
+++ b/RyuSocks.Test/Auth/NoAuthTests.cs
@@ -18,10 +18,12 @@
using RyuSocks.Auth.Extensions;
using RyuSocks.Types;
using System;
+using System.Diagnostics.CodeAnalysis;
using Xunit;
namespace RyuSocks.Test.Auth
{
+ [ExcludeFromCodeCoverage]
public class NoAuthTests
{
[Theory]
diff --git a/RyuSocks.Test/Auth/UsernameAndPasswordRequestTests.cs b/RyuSocks.Test/Auth/UsernameAndPasswordRequestTests.cs
index 384c2c5..8cb0066 100644
--- a/RyuSocks.Test/Auth/UsernameAndPasswordRequestTests.cs
+++ b/RyuSocks.Test/Auth/UsernameAndPasswordRequestTests.cs
@@ -1,9 +1,11 @@
using RyuSocks.Auth.Packets;
using System;
+using System.Diagnostics.CodeAnalysis;
using Xunit;
namespace RyuSocks.Test.Auth
{
+ [ExcludeFromCodeCoverage]
public class UsernameAndPasswordRequestTests
{
private const string LongWord = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
diff --git a/RyuSocks.Test/Auth/UsernameAndPasswordResponseTest.cs b/RyuSocks.Test/Auth/UsernameAndPasswordResponseTest.cs
index 42215d7..bb44c3b 100644
--- a/RyuSocks.Test/Auth/UsernameAndPasswordResponseTest.cs
+++ b/RyuSocks.Test/Auth/UsernameAndPasswordResponseTest.cs
@@ -1,10 +1,12 @@
using RyuSocks.Auth;
using RyuSocks.Auth.Packets;
using System;
+using System.Diagnostics.CodeAnalysis;
using Xunit;
namespace RyuSocks.Test.Auth
{
+ [ExcludeFromCodeCoverage]
public class UsernameAndPasswordResponseTest
{
[Theory]
diff --git a/RyuSocks.Test/Auth/UsernameAndPasswordTests.cs b/RyuSocks.Test/Auth/UsernameAndPasswordTests.cs
index 7b83644..062173b 100644
--- a/RyuSocks.Test/Auth/UsernameAndPasswordTests.cs
+++ b/RyuSocks.Test/Auth/UsernameAndPasswordTests.cs
@@ -18,10 +18,12 @@
using RyuSocks.Auth.Packets;
using RyuSocks.Types;
using System;
+using System.Diagnostics.CodeAnalysis;
using Xunit;
namespace RyuSocks.Test.Auth
{
+ [ExcludeFromCodeCoverage]
public class UsernameAndPasswordTests
{
private static readonly UsernameAndPasswordResponse _expectedUsernameAndPasswordResponse = new()
diff --git a/RyuSocks.Test/Packets/CommandPacketTests.cs b/RyuSocks.Test/Packets/CommandPacketTests.cs
index 5df2fb3..c5215a5 100644
--- a/RyuSocks.Test/Packets/CommandPacketTests.cs
+++ b/RyuSocks.Test/Packets/CommandPacketTests.cs
@@ -18,6 +18,7 @@
using RyuSocks.Types;
using RyuSocks.Utils;
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net;
using System.Text;
@@ -25,6 +26,7 @@
namespace RyuSocks.Test.Packets
{
+ [ExcludeFromCodeCoverage]
public abstract class CommandPacketTests where T : CommandPacket
{
private const string VeryLongInvalidTestDomainName = "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc.local";
diff --git a/RyuSocks.Test/Packets/CommandRequestTests.cs b/RyuSocks.Test/Packets/CommandRequestTests.cs
index 84088b7..444fef9 100644
--- a/RyuSocks.Test/Packets/CommandRequestTests.cs
+++ b/RyuSocks.Test/Packets/CommandRequestTests.cs
@@ -20,6 +20,7 @@
using RyuSocks.Types;
using RyuSocks.Utils;
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net;
using System.Net.Sockets;
@@ -28,6 +29,7 @@
namespace RyuSocks.Test.Packets
{
+ [ExcludeFromCodeCoverage]
public class CommandRequestTests : CommandPacketTests
{
#pragma warning disable IDE0055 // Disable formatting
diff --git a/RyuSocks.Test/Packets/CommandResponseTests.cs b/RyuSocks.Test/Packets/CommandResponseTests.cs
index 5e41bbb..0f8c642 100644
--- a/RyuSocks.Test/Packets/CommandResponseTests.cs
+++ b/RyuSocks.Test/Packets/CommandResponseTests.cs
@@ -19,6 +19,7 @@
using RyuSocks.Types;
using RyuSocks.Utils;
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net;
using System.Net.Sockets;
@@ -27,6 +28,7 @@
namespace RyuSocks.Test.Packets
{
+ [ExcludeFromCodeCoverage]
public class CommandResponseTests : CommandPacketTests
{
#pragma warning disable IDE0055 // Disable formatting
diff --git a/RyuSocks.Test/Packets/MethodSelectionRequestTests.cs b/RyuSocks.Test/Packets/MethodSelectionRequestTests.cs
index 79eaac3..a5604af 100644
--- a/RyuSocks.Test/Packets/MethodSelectionRequestTests.cs
+++ b/RyuSocks.Test/Packets/MethodSelectionRequestTests.cs
@@ -19,11 +19,13 @@
using RyuSocks.Test.Utils;
using RyuSocks.Utils;
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Xunit;
namespace RyuSocks.Test.Packets
{
+ [ExcludeFromCodeCoverage]
public class MethodSelectionRequestTests
{
[Theory]
diff --git a/RyuSocks.Test/Packets/MethodSelectionResponseTests.cs b/RyuSocks.Test/Packets/MethodSelectionResponseTests.cs
index ac0ab4e..e0f073a 100644
--- a/RyuSocks.Test/Packets/MethodSelectionResponseTests.cs
+++ b/RyuSocks.Test/Packets/MethodSelectionResponseTests.cs
@@ -18,10 +18,12 @@
using RyuSocks.Packets;
using RyuSocks.Utils;
using System;
+using System.Diagnostics.CodeAnalysis;
using Xunit;
namespace RyuSocks.Test.Packets
{
+ [ExcludeFromCodeCoverage]
public class MethodSelectionResponseTests
{
[Fact]
diff --git a/RyuSocks.Test/SocksClientTests.cs b/RyuSocks.Test/SocksClientTests.cs
index 1d1334d..38476b6 100644
--- a/RyuSocks.Test/SocksClientTests.cs
+++ b/RyuSocks.Test/SocksClientTests.cs
@@ -21,6 +21,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net;
using System.Threading;
@@ -28,6 +29,7 @@
namespace RyuSocks.Test
{
+ [ExcludeFromCodeCoverage]
public class SocksClientTests : IDisposable, IClassFixture
{
private readonly SocksServerFixture _fixture;
@@ -80,6 +82,7 @@ public void Authenticate_Succeeds()
}
// ReSharper disable once ClassNeverInstantiated.Global
+ [ExcludeFromCodeCoverage]
public class SocksServerFixture : IDisposable
{
public TestSocksServer Server { get; }
@@ -112,6 +115,7 @@ public void Dispose()
}
}
+ [ExcludeFromCodeCoverage]
public class TestSocksServer : SocksServer
{
public TestSocksServer(IPAddress address, ushort port = ProxyConsts.DefaultPort) : base(address, port) { }
diff --git a/RyuSocks.Test/Utils/EnumDataAttribute.cs b/RyuSocks.Test/Utils/EnumDataAttribute.cs
index b95e632..927b202 100644
--- a/RyuSocks.Test/Utils/EnumDataAttribute.cs
+++ b/RyuSocks.Test/Utils/EnumDataAttribute.cs
@@ -14,6 +14,7 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Threading.Tasks;
using Xunit;
@@ -22,6 +23,7 @@
namespace RyuSocks.Test.Utils
{
+ [ExcludeFromCodeCoverage]
public class EnumDataAttribute : DataAttribute
where T : struct, Enum
{
diff --git a/RyuSocks.Test/Utils/RangeDataAttribute.cs b/RyuSocks.Test/Utils/RangeDataAttribute.cs
index 47faa48..0b39302 100644
--- a/RyuSocks.Test/Utils/RangeDataAttribute.cs
+++ b/RyuSocks.Test/Utils/RangeDataAttribute.cs
@@ -13,6 +13,7 @@
// along with this program. If not, see .
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Reflection;
using System.Threading.Tasks;
@@ -22,6 +23,7 @@
namespace RyuSocks.Test.Utils
{
+ [ExcludeFromCodeCoverage]
public class RangeDataAttribute : DataAttribute
where T : INumber
{
diff --git a/RyuSocks.Test/Utils/StringDataAttribute.cs b/RyuSocks.Test/Utils/StringDataAttribute.cs
index 6c5a629..947c76b 100644
--- a/RyuSocks.Test/Utils/StringDataAttribute.cs
+++ b/RyuSocks.Test/Utils/StringDataAttribute.cs
@@ -15,6 +15,7 @@
*/
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
@@ -24,6 +25,7 @@
namespace RyuSocks.Test.Utils
{
+ [ExcludeFromCodeCoverage]
public class StringDataAttribute : DataAttribute
{
private const char Char = 'a';
diff --git a/RyuSocks.sln b/RyuSocks.sln
index 63af2ad..eadf7f0 100644
--- a/RyuSocks.sln
+++ b/RyuSocks.sln
@@ -8,6 +8,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RyuSocks.Test", "RyuSocks.T
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RyuSocks.Test.Integration", "RyuSocks.Test.Integration\RyuSocks.Test.Integration.csproj", "{C0D6DEF0-9544-454A-A009-227EC1BEDCE3}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RyuSocks.Generator.Test", "RyuSocks.Generator.Test\RyuSocks.Generator.Test.csproj", "{6B63A81D-99FF-4DFF-BA12-5BB173E4E36F}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -30,5 +32,9 @@ Global
{C0D6DEF0-9544-454A-A009-227EC1BEDCE3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C0D6DEF0-9544-454A-A009-227EC1BEDCE3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C0D6DEF0-9544-454A-A009-227EC1BEDCE3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6B63A81D-99FF-4DFF-BA12-5BB173E4E36F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6B63A81D-99FF-4DFF-BA12-5BB173E4E36F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6B63A81D-99FF-4DFF-BA12-5BB173E4E36F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6B63A81D-99FF-4DFF-BA12-5BB173E4E36F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/RyuSocks/Auth/Packets/UsernameAndPasswordRequest.cs b/RyuSocks/Auth/Packets/UsernameAndPasswordRequest.cs
index 8b70c81..af0772d 100644
--- a/RyuSocks/Auth/Packets/UsernameAndPasswordRequest.cs
+++ b/RyuSocks/Auth/Packets/UsernameAndPasswordRequest.cs
@@ -16,78 +16,31 @@
using RyuSocks.Packets;
using System;
-using System.Text;
namespace RyuSocks.Auth.Packets
{
- public class UsernameAndPasswordRequest : Packet
+ public partial class UsernameAndPasswordRequest : Packet
{
private const int MinimumPacketLength = 4;
private const int MaximumPacketLength = 513;
- public byte Version
- {
- get
- {
- return Bytes[0];
- }
- set
- {
- Bytes[0] = value;
- }
- }
+ [PacketField(0)]
+ public partial byte Version { get; set; }
- public byte UsernameLength
- {
- get
- {
- return Bytes[1];
- }
- set
- {
- Bytes[1] = value;
- }
- }
+ [PacketField(1)]
+ public partial byte UsernameLength { get; set; }
- public string Username
- {
- get
- {
- return Encoding.ASCII.GetString(Bytes.AsSpan(2, UsernameLength));
- }
- set
- {
- ArgumentOutOfRangeException.ThrowIfGreaterThan(value.Length, 0xFF);
- UsernameLength = (byte)value.Length;
- Encoding.ASCII.GetBytes(value).CopyTo(Bytes.AsSpan(2, UsernameLength));
- }
- }
+ [PacketField(2, LengthMember = nameof(UsernameLength), MaxLength = 0xFF)]
+ public partial string Username { get; set; }
- public byte PasswordLength
- {
- get
- {
- return Bytes[2 + UsernameLength];
- }
- set
- {
- Bytes[2 + UsernameLength] = value;
- }
- }
+ [PacketField(nameof(PasswordLengthOffset))]
+ public partial byte PasswordLength { get; set; }
- public string Password
- {
- get
- {
- return Encoding.ASCII.GetString(Bytes.AsSpan(3 + UsernameLength, PasswordLength));
- }
- set
- {
- ArgumentOutOfRangeException.ThrowIfGreaterThan(value.Length, 0xFF);
- PasswordLength = (byte)value.Length;
- Encoding.ASCII.GetBytes(value).CopyTo(Bytes.AsSpan(3 + UsernameLength, PasswordLength));
- }
- }
+ [PacketField(nameof(PasswordOffset), LengthMember = nameof(PasswordLength), MaxLength = 0xFF)]
+ public partial string Password { get; set; }
+
+ private int PasswordLengthOffset => 2 + UsernameLength;
+ private int PasswordOffset => PasswordLengthOffset + 1;
public UsernameAndPasswordRequest(byte[] packetBytes) : base(packetBytes)
{
diff --git a/RyuSocks/Auth/Packets/UsernameAndPasswordResponse.cs b/RyuSocks/Auth/Packets/UsernameAndPasswordResponse.cs
index 2a95be1..7bd02ee 100644
--- a/RyuSocks/Auth/Packets/UsernameAndPasswordResponse.cs
+++ b/RyuSocks/Auth/Packets/UsernameAndPasswordResponse.cs
@@ -19,31 +19,13 @@
namespace RyuSocks.Auth.Packets
{
- public class UsernameAndPasswordResponse : Packet
+ public partial class UsernameAndPasswordResponse : Packet
{
- public byte Version
- {
- get
- {
- return Bytes[0];
- }
- set
- {
- Bytes[0] = value;
- }
- }
+ [PacketField(0)]
+ public partial byte Version { get; set; }
- public byte Status
- {
- get
- {
- return Bytes[1];
- }
- set
- {
- Bytes[1] = value;
- }
- }
+ [PacketField(1)]
+ public partial byte Status { get; set; }
public UsernameAndPasswordResponse()
{
diff --git a/RyuSocks/Packets/CommandPacket.cs b/RyuSocks/Packets/CommandPacket.cs
index 6e8c117..802cca2 100644
--- a/RyuSocks/Packets/CommandPacket.cs
+++ b/RyuSocks/Packets/CommandPacket.cs
@@ -21,33 +21,15 @@
namespace RyuSocks.Packets
{
- public abstract class CommandPacket : EndpointPacket
+ public abstract partial class CommandPacket : EndpointPacket
{
- public byte Version
- {
- get
- {
- return Bytes[0];
- }
- set
- {
- Bytes[0] = value;
- }
- }
+ [PacketField(0)]
+ public partial byte Version { get; set; }
// ProxyCommand or ReplyField
- public byte Reserved
- {
- get
- {
- return Bytes[2];
- }
- set
- {
- Bytes[2] = value;
- }
- }
+ [PacketField(2)]
+ public partial byte Reserved { get; set; }
// AddressType
@@ -56,9 +38,13 @@ public byte Reserved
// Port
protected CommandPacket(byte[] bytes) : base(bytes) { }
+
protected CommandPacket(IPEndPoint endpoint) : base(endpoint) { }
+
protected CommandPacket(DnsEndPoint endpoint) : base(endpoint) { }
+
protected CommandPacket(ProxyEndpoint endpoint) : base(endpoint) { }
+
protected CommandPacket() { }
public override void Validate()
diff --git a/RyuSocks/Packets/CommandRequest.cs b/RyuSocks/Packets/CommandRequest.cs
index aca1644..4a5bd20 100644
--- a/RyuSocks/Packets/CommandRequest.cs
+++ b/RyuSocks/Packets/CommandRequest.cs
@@ -16,25 +16,17 @@
using RyuSocks.Commands;
using RyuSocks.Types;
+using System;
using System.Net;
namespace RyuSocks.Packets
{
- public class CommandRequest : CommandPacket
+ public partial class CommandRequest : CommandPacket
{
// Version
- public ProxyCommand Command
- {
- get
- {
- return (ProxyCommand)Bytes[1];
- }
- set
- {
- Bytes[1] = (byte)value;
- }
- }
+ [PacketField(1, AssumeGeneratedEnumType = nameof(Byte))]
+ public partial ProxyCommand Command { get; set; }
// Reserved
@@ -59,9 +51,13 @@ public ushort DestinationPort
}
public CommandRequest(byte[] bytes) : base(bytes) { }
+
public CommandRequest(IPEndPoint endpoint) : base(endpoint) { }
+
public CommandRequest(DnsEndPoint endpoint) : base(endpoint) { }
+
public CommandRequest(ProxyEndpoint endpoint) : base(endpoint) { }
+
public CommandRequest() { }
}
}
diff --git a/RyuSocks/Packets/CommandResponse.cs b/RyuSocks/Packets/CommandResponse.cs
index 3f9ac80..bd5f59d 100644
--- a/RyuSocks/Packets/CommandResponse.cs
+++ b/RyuSocks/Packets/CommandResponse.cs
@@ -19,21 +19,12 @@
namespace RyuSocks.Packets
{
- public class CommandResponse : CommandPacket
+ public partial class CommandResponse : CommandPacket
{
// Version
- public ReplyField ReplyField
- {
- get
- {
- return (ReplyField)Bytes[1];
- }
- set
- {
- Bytes[1] = (byte)value;
- }
- }
+ [PacketField(1)]
+ public partial ReplyField ReplyField { get; set; }
// Reserved
@@ -58,9 +49,13 @@ public ushort BoundPort
}
public CommandResponse(byte[] bytes) : base(bytes) { }
+
public CommandResponse(IPEndPoint endpoint) : base(endpoint) { }
+
public CommandResponse(DnsEndPoint endpoint) : base(endpoint) { }
+
public CommandResponse(ProxyEndpoint endpoint) : base(endpoint) { }
+
public CommandResponse() { }
}
}
diff --git a/RyuSocks/Packets/EndpointPacket.cs b/RyuSocks/Packets/EndpointPacket.cs
index 3be0588..b679ad9 100644
--- a/RyuSocks/Packets/EndpointPacket.cs
+++ b/RyuSocks/Packets/EndpointPacket.cs
@@ -19,11 +19,10 @@
using System.Linq;
using System.Net;
using System.Net.Sockets;
-using System.Text;
namespace RyuSocks.Packets
{
- public abstract class EndpointPacket : Packet
+ public abstract partial class EndpointPacket : Packet
{
private const byte MinimumPacketLength = 8;
private const byte Ipv4PacketLength = 10;
@@ -31,136 +30,48 @@ public abstract class EndpointPacket : Packet
private const byte MinimumDomainNameLength = byte.MinValue + 1;
private const byte MaximumDomainNameLength = byte.MaxValue;
- public AddressType AddressType
- {
- get
- {
- return (AddressType)Bytes[3];
- }
- set
- {
- Bytes[3] = (byte)value;
+ [PacketField(3, ValidationMethod = nameof(ResizeIfNecessary))]
+ public partial AddressType AddressType { get; set; }
- if (AddressType is AddressType.Ipv4Address or AddressType.Ipv6Address)
- {
- int requiredLength = GetEndpointPacketLength();
- if (Bytes.Length != requiredLength)
- {
- byte[] resizeBytes = Bytes;
- Array.Resize(ref resizeBytes, requiredLength);
- Bytes = resizeBytes;
- }
- }
- }
- }
+ [PacketField(4, LengthMember = nameof(AddressSize))]
+ protected partial IPAddress Address { get; set; }
- protected IPAddress Address
- {
- get
- {
- return AddressType switch
- {
- AddressType.Ipv4Address => new IPAddress(Bytes[4..8]),
- AddressType.Ipv6Address => new IPAddress(Bytes[4..20]),
- _ => throw new InvalidOperationException(
- $"Can't get {nameof(Address)} for {nameof(Types.AddressType)} {AddressType}."),
- };
- }
- set
- {
- switch (AddressType)
- {
- case AddressType.Ipv4Address:
- value.GetAddressBytes().CopyTo(Bytes.AsSpan(4, 4));
- return;
- case AddressType.Ipv6Address:
- value.GetAddressBytes().CopyTo(Bytes.AsSpan(4, 16));
- return;
- default:
- throw new InvalidOperationException(
- $"Can't set {nameof(Address)} for {nameof(Types.AddressType)} {AddressType}.");
- }
- }
- }
+ [PacketField(4, ValidationMethod = nameof(ResizeForDomainNameIfNecessary))]
+ protected partial byte DomainNameLength { get; set; }
- protected byte DomainNameLength
- {
- get
- {
- return AddressType switch
- {
- AddressType.DomainName => Bytes[4],
- _ => throw new InvalidOperationException(
- $"Can't get {nameof(DomainNameLength)} for {nameof(Types.AddressType)} {AddressType}."),
- };
- }
- set
- {
- if (AddressType != AddressType.DomainName)
- {
- throw new InvalidOperationException(
- $"Can't set {nameof(DomainNameLength)} for {nameof(Types.AddressType)} {AddressType}.");
- }
+ [PacketField(5, LengthMember = nameof(DomainNameLength), MinLength = MinimumDomainNameLength, MaxLength = MaximumDomainNameLength, ValidationMethod = nameof(EnsureDomainNameType))]
+ protected partial string DomainName { get; set; }
- ArgumentOutOfRangeException.ThrowIfLessThan(value, MinimumDomainNameLength);
- Bytes[4] = value;
+ [PacketField(nameof(PortOffset), IsBigEndian = true)]
+ protected partial ushort Port { get; set; }
- int requiredLength = GetEndpointPacketLength();
- if (Bytes.Length != requiredLength)
- {
- byte[] resizeBytes = Bytes;
- Array.Resize(ref resizeBytes, requiredLength);
- Bytes = resizeBytes;
- }
- }
- }
+ public ProxyEndpoint ProxyEndpoint => AddressType == AddressType.DomainName
+ ? new ProxyEndpoint(new DnsEndPoint(DomainName, Port))
+ : new ProxyEndpoint(new IPEndPoint(Address, Port));
- protected string DomainName
+ protected int PacketLength => AddressType switch
{
- get
- {
- return AddressType switch
- {
- AddressType.DomainName => Encoding.ASCII.GetString(Bytes, 5, DomainNameLength),
- _ => throw new InvalidOperationException(
- $"Can't get {nameof(DomainName)} for {nameof(Types.AddressType)} {AddressType}.")
- };
- }
- set
- {
- switch (AddressType)
- {
- case AddressType.DomainName:
- ArgumentOutOfRangeException.ThrowIfGreaterThan(value.Length, MaximumDomainNameLength);
- DomainNameLength = (byte)value.Length;
- Encoding.ASCII.GetBytes(value, Bytes.AsSpan(5, DomainNameLength));
- return;
- default:
- throw new InvalidOperationException(
- $"Can't set {nameof(DomainName)} for {nameof(Types.AddressType)} {AddressType}.");
- }
- }
- }
+ AddressType.Ipv4Address => Ipv4PacketLength,
+ AddressType.DomainName => 7 + DomainNameLength,
+ AddressType.Ipv6Address => Ipv6PacketLength,
+ _ => throw new ArgumentOutOfRangeException(nameof(AddressType)),
+ };
- protected ushort Port
+ private int AddressSize => AddressType switch
{
- get
- {
- Span portSpan = GetPortSpan();
- portSpan.Reverse();
- return BitConverter.ToUInt16(portSpan);
- }
- set
- {
- byte[] portBytes = BitConverter.GetBytes(value);
- Array.Reverse(portBytes);
- portBytes.CopyTo(GetPortSpan());
- }
- }
+ AddressType.Ipv4Address => 4,
+ AddressType.Ipv6Address => 16,
+ _ => throw new InvalidOperationException(
+ $"Can't get address size for {nameof(Types.AddressType)} {AddressType}."),
+ };
- public ProxyEndpoint ProxyEndpoint => AddressType == AddressType.DomainName
- ? new ProxyEndpoint(new DnsEndPoint(DomainName, Port))
- : new ProxyEndpoint(new IPEndPoint(Address, Port));
+ private int PortOffset => AddressType switch
+ {
+ AddressType.Ipv4Address => Ipv4PacketLength - 2,
+ AddressType.DomainName => 5 + DomainNameLength,
+ AddressType.Ipv6Address => Ipv6PacketLength - 2,
+ _ => throw new ArgumentOutOfRangeException(nameof(AddressType)),
+ };
protected EndpointPacket(byte[] bytes) : base(bytes)
{
@@ -234,26 +145,51 @@ protected EndpointPacket()
AddressType = AddressType.Ipv4Address;
}
- protected int GetEndpointPacketLength()
+ // ReSharper disable once UnusedParameter.Local
+ private void EnsureDomainNameType(string value = "")
{
- return AddressType switch
+ if (AddressType != AddressType.DomainName)
{
- AddressType.Ipv4Address => Ipv4PacketLength,
- AddressType.DomainName => 7 + DomainNameLength,
- AddressType.Ipv6Address => Ipv6PacketLength,
- _ => throw new ArgumentOutOfRangeException(nameof(AddressType)),
- };
+ throw new InvalidOperationException(
+ $"Can't get {nameof(DomainNameLength)} for {nameof(Types.AddressType)} {AddressType}.");
+ }
}
- private Span GetPortSpan()
+ private void ResizeIfNecessary(AddressType value = 0)
{
- return AddressType switch
+ if (value == 0)
{
- AddressType.Ipv4Address => Bytes.AsSpan(Ipv4PacketLength - 2, 2),
- AddressType.DomainName => Bytes.AsSpan(5 + DomainNameLength, 2),
- AddressType.Ipv6Address => Bytes.AsSpan(Ipv6PacketLength - 2, 2),
- _ => throw new ArgumentOutOfRangeException(nameof(AddressType)),
- };
+ return;
+ }
+
+ if (AddressType is AddressType.Ipv4Address or AddressType.Ipv6Address)
+ {
+ int requiredLength = PacketLength;
+ if (Bytes.Length != requiredLength)
+ {
+ byte[] resizeBytes = Bytes;
+ Array.Resize(ref resizeBytes, requiredLength);
+ Bytes = resizeBytes;
+ }
+ }
+ }
+
+ private void ResizeForDomainNameIfNecessary(byte value = 0)
+ {
+ EnsureDomainNameType();
+
+ if (value == 0)
+ {
+ return;
+ }
+
+ int requiredLength = 7 + value;
+ if (Bytes.Length != requiredLength)
+ {
+ byte[] resizeBytes = Bytes;
+ Array.Resize(ref resizeBytes, requiredLength);
+ Bytes = resizeBytes;
+ }
}
public override void Validate()
diff --git a/RyuSocks/Packets/MethodSelectionRequest.cs b/RyuSocks/Packets/MethodSelectionRequest.cs
index 091b438..1132724 100644
--- a/RyuSocks/Packets/MethodSelectionRequest.cs
+++ b/RyuSocks/Packets/MethodSelectionRequest.cs
@@ -21,46 +21,16 @@
namespace RyuSocks.Packets
{
- public class MethodSelectionRequest : Packet
+ public partial class MethodSelectionRequest : Packet
{
- public byte Version
- {
- get
- {
- return Bytes[0];
- }
- set
- {
- Bytes[0] = value;
- }
- }
+ [PacketField(0)]
+ public partial byte Version { get; set; }
- public byte NumOfMethods
- {
- get
- {
- return Bytes[1];
- }
- set
- {
- Bytes[1] = value;
- }
- }
-
- public AuthMethod[] Methods
- {
- get
- {
- return Bytes[2..(2 + NumOfMethods)].Cast().ToArray();
+ [PacketField(1)]
+ public partial byte NumOfMethods { get; set; }
- }
- set
- {
- ArgumentOutOfRangeException.ThrowIfGreaterThan(value.Length, 0xFF);
- NumOfMethods = (byte)value.Length;
- value.Cast().ToArray().CopyTo(Bytes.AsSpan(2, value.Length));
- }
- }
+ [PacketField(2, LengthMember = nameof(NumOfMethods), MaxLength = 0xFF, AssumeGeneratedEnumType = nameof(Byte))]
+ public partial AuthMethod[] Methods { get; set; }
public MethodSelectionRequest(byte[] bytes) : base(bytes) { }
diff --git a/RyuSocks/Packets/MethodSelectionResponse.cs b/RyuSocks/Packets/MethodSelectionResponse.cs
index 95eff39..3ff310b 100644
--- a/RyuSocks/Packets/MethodSelectionResponse.cs
+++ b/RyuSocks/Packets/MethodSelectionResponse.cs
@@ -20,31 +20,13 @@
namespace RyuSocks.Packets
{
- public class MethodSelectionResponse : Packet
+ public partial class MethodSelectionResponse : Packet
{
- public byte Version
- {
- get
- {
- return Bytes[0];
- }
- set
- {
- Bytes[0] = value;
- }
- }
+ [PacketField(0)]
+ public partial byte Version { get; set; }
- public AuthMethod Method
- {
- get
- {
- return (AuthMethod)Bytes[1];
- }
- set
- {
- Bytes[1] = (byte)value;
- }
- }
+ [PacketField(1, AssumeGeneratedEnumType = nameof(Byte))]
+ public partial AuthMethod Method { get; set; }
public MethodSelectionResponse(byte[] bytes) : base(bytes) { }
diff --git a/RyuSocks/Packets/Packet.cs b/RyuSocks/Packets/Packet.cs
index 8d0282e..781893b 100644
--- a/RyuSocks/Packets/Packet.cs
+++ b/RyuSocks/Packets/Packet.cs
@@ -14,20 +14,10 @@
* along with this program. If not, see .
*/
-using System;
-
namespace RyuSocks.Packets
{
- public abstract class Packet
+ public abstract partial class Packet
{
- ///
- /// The contents of the packet.
- ///
- public byte[] Bytes { get; protected set; }
-
- ///
- public Span AsSpan() => Bytes;
-
///
/// Validate the structure of the packet.
/// This method is not supposed to verify the contents of the packet in depth.
@@ -52,12 +42,5 @@ public bool IsValid()
return true;
}
-
- protected Packet() { }
-
- protected Packet(byte[] bytes)
- {
- Bytes = bytes;
- }
}
}
diff --git a/RyuSocks/Packets/UdpPacket.cs b/RyuSocks/Packets/UdpPacket.cs
index 5bd4120..093d6fa 100644
--- a/RyuSocks/Packets/UdpPacket.cs
+++ b/RyuSocks/Packets/UdpPacket.cs
@@ -20,33 +20,15 @@
namespace RyuSocks.Packets
{
- public class UdpPacket : EndpointPacket
+ public partial class UdpPacket : EndpointPacket
{
- public int HeaderLength => GetEndpointPacketLength();
+ public int HeaderLength => PacketLength;
- public ushort Reserved
- {
- get
- {
- return BitConverter.ToUInt16(Bytes.AsSpan(0, 2));
- }
- set
- {
- BitConverter.GetBytes(value).CopyTo(Bytes.AsSpan(0, 2));
- }
- }
+ [PacketField(0)]
+ public partial ushort Reserved { get; set; }
- public byte Fragment
- {
- get
- {
- return Bytes[2];
- }
- set
- {
- Bytes[2] = value;
- }
- }
+ [PacketField(2)]
+ public partial byte Fragment { get; set; }
// AddressType
@@ -68,7 +50,7 @@ public ushort DestinationPort
set => Port = value;
}
- public Span UserData => Bytes.AsSpan(GetEndpointPacketLength());
+ public Span UserData => Bytes.AsSpan(PacketLength);
public UdpPacket(byte[] bytes) : base(bytes) { }