diff --git a/src/OpenRGB.NET.Example/Program.cs b/src/OpenRGB.NET.Example/Program.cs
index 40849d4..6c6ad9f 100644
--- a/src/OpenRGB.NET.Example/Program.cs
+++ b/src/OpenRGB.NET.Example/Program.cs
@@ -44,7 +44,7 @@
client.UpdateLeds(index, slice);
}
- Thread.Sleep(1000 / fps);
+ Thread.Sleep(1000 / fps);
}
});
diff --git a/src/OpenRGB.NET.Tests/BufferReadTests.cs b/src/OpenRGB.NET.Tests/BufferReadTests.cs
new file mode 100644
index 0000000..09b0e81
--- /dev/null
+++ b/src/OpenRGB.NET.Tests/BufferReadTests.cs
@@ -0,0 +1,19 @@
+using System.IO;
+using OpenRGB.NET.Utils;
+using Xunit;
+
+namespace OpenRGB.NET.Test;
+
+public class BufferReadTests
+{
+ [Fact]
+ public void Test_Read_Device()
+ {
+ var buffer = File.ReadAllBytes(Path.Combine("TestData", "08-Receive-RequestControllerData.bin"));
+ var spanReader = new SpanReader(buffer);
+ var device = DeviceReader.ReadFrom(ref spanReader, ProtocolVersion.V4, 0);
+
+ Assert.Equal(0, device.Index);
+ Assert.Equal("Full 104 key ", device.Name);
+ }
+}
\ No newline at end of file
diff --git a/src/OpenRGB.NET.Tests/OpenRGB.NET.Tests.csproj b/src/OpenRGB.NET.Tests/OpenRGB.NET.Tests.csproj
index a3a173e..bacabd8 100644
--- a/src/OpenRGB.NET.Tests/OpenRGB.NET.Tests.csproj
+++ b/src/OpenRGB.NET.Tests/OpenRGB.NET.Tests.csproj
@@ -24,5 +24,9 @@
+
+
+
+
diff --git a/src/OpenRGB.NET.Tests/TestData/00-Send-SetClientName.bin b/src/OpenRGB.NET.Tests/TestData/00-Send-SetClientName.bin
new file mode 100644
index 0000000..41096b9
Binary files /dev/null and b/src/OpenRGB.NET.Tests/TestData/00-Send-SetClientName.bin differ
diff --git a/src/OpenRGB.NET.Tests/TestData/01-Send-RequestProtocolVersion.bin b/src/OpenRGB.NET.Tests/TestData/01-Send-RequestProtocolVersion.bin
new file mode 100644
index 0000000..7ad54b4
Binary files /dev/null and b/src/OpenRGB.NET.Tests/TestData/01-Send-RequestProtocolVersion.bin differ
diff --git a/src/OpenRGB.NET.Tests/TestData/02-Receive-RequestProtocolVersion.bin b/src/OpenRGB.NET.Tests/TestData/02-Receive-RequestProtocolVersion.bin
new file mode 100644
index 0000000..86906d2
Binary files /dev/null and b/src/OpenRGB.NET.Tests/TestData/02-Receive-RequestProtocolVersion.bin differ
diff --git a/src/OpenRGB.NET.Tests/TestData/03-Send-RequestPlugins.bin b/src/OpenRGB.NET.Tests/TestData/03-Send-RequestPlugins.bin
new file mode 100644
index 0000000..fbc897f
Binary files /dev/null and b/src/OpenRGB.NET.Tests/TestData/03-Send-RequestPlugins.bin differ
diff --git a/src/OpenRGB.NET.Tests/TestData/04-Receive-RequestPlugins.bin b/src/OpenRGB.NET.Tests/TestData/04-Receive-RequestPlugins.bin
new file mode 100644
index 0000000..4be6b17
Binary files /dev/null and b/src/OpenRGB.NET.Tests/TestData/04-Receive-RequestPlugins.bin differ
diff --git a/src/OpenRGB.NET.Tests/TestData/05-Send-RequestControllerCount.bin b/src/OpenRGB.NET.Tests/TestData/05-Send-RequestControllerCount.bin
new file mode 100644
index 0000000..6455f43
Binary files /dev/null and b/src/OpenRGB.NET.Tests/TestData/05-Send-RequestControllerCount.bin differ
diff --git a/src/OpenRGB.NET.Tests/TestData/06-Receive-RequestControllerCount.bin b/src/OpenRGB.NET.Tests/TestData/06-Receive-RequestControllerCount.bin
new file mode 100644
index 0000000..f66c9cf
Binary files /dev/null and b/src/OpenRGB.NET.Tests/TestData/06-Receive-RequestControllerCount.bin differ
diff --git a/src/OpenRGB.NET.Tests/TestData/07-Send-RequestControllerData.bin b/src/OpenRGB.NET.Tests/TestData/07-Send-RequestControllerData.bin
new file mode 100644
index 0000000..a16d3f4
Binary files /dev/null and b/src/OpenRGB.NET.Tests/TestData/07-Send-RequestControllerData.bin differ
diff --git a/src/OpenRGB.NET.Tests/TestData/08-Receive-RequestControllerData.bin b/src/OpenRGB.NET.Tests/TestData/08-Receive-RequestControllerData.bin
new file mode 100644
index 0000000..7af5791
Binary files /dev/null and b/src/OpenRGB.NET.Tests/TestData/08-Receive-RequestControllerData.bin differ
diff --git a/src/OpenRGB.NET.Tests/TestData/09-Send-RequestProfiles.bin b/src/OpenRGB.NET.Tests/TestData/09-Send-RequestProfiles.bin
new file mode 100644
index 0000000..3980d44
Binary files /dev/null and b/src/OpenRGB.NET.Tests/TestData/09-Send-RequestProfiles.bin differ
diff --git a/src/OpenRGB.NET.Tests/TestData/10-Receive-RequestProfiles.bin b/src/OpenRGB.NET.Tests/TestData/10-Receive-RequestProfiles.bin
new file mode 100644
index 0000000..ac24d35
Binary files /dev/null and b/src/OpenRGB.NET.Tests/TestData/10-Receive-RequestProfiles.bin differ
diff --git a/src/OpenRGB.NET/IOpenRGBClient.cs b/src/OpenRGB.NET/IOpenRGBClient.cs
index c19d41e..35a0010 100644
--- a/src/OpenRGB.NET/IOpenRGBClient.cs
+++ b/src/OpenRGB.NET/IOpenRGBClient.cs
@@ -16,7 +16,7 @@ public interface IOpenRgbClient
/// The maximum protocol version this implementation supports
///
ProtocolVersion MaxSupportedProtocolVersion { get; }
-
+
///
/// The protocol version to be used by this instance of
///
diff --git a/src/OpenRGB.NET/ISpanReader.cs b/src/OpenRGB.NET/ISpanReader.cs
new file mode 100644
index 0000000..90d2c14
--- /dev/null
+++ b/src/OpenRGB.NET/ISpanReader.cs
@@ -0,0 +1,8 @@
+using OpenRGB.NET.Utils;
+
+namespace OpenRGB.NET;
+
+internal interface ISpanReader
+{
+ static abstract T ReadFrom(ref SpanReader reader, ProtocolVersion? protocolVersion = default, int? index = default, int? outerCount = default);
+}
\ No newline at end of file
diff --git a/src/OpenRGB.NET/ISpanWritable.cs b/src/OpenRGB.NET/ISpanWritable.cs
new file mode 100644
index 0000000..9202faa
--- /dev/null
+++ b/src/OpenRGB.NET/ISpanWritable.cs
@@ -0,0 +1,9 @@
+using OpenRGB.NET.Utils;
+
+namespace OpenRGB.NET;
+
+internal interface ISpanWritable
+{
+ int Length { get; }
+ void WriteTo(ref SpanWriter writer);
+}
\ No newline at end of file
diff --git a/src/OpenRGB.NET/IsExternalInit.cs b/src/OpenRGB.NET/IsExternalInit.cs
new file mode 100644
index 0000000..1c4db9b
--- /dev/null
+++ b/src/OpenRGB.NET/IsExternalInit.cs
@@ -0,0 +1,22 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#if NETSTANDARD2_0 || NETSTANDARD2_1 || NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 || NETCOREAPP3_0 || NETCOREAPP3_1 || NET45 || NET451 || NET452 || NET46 || NET461 || NET462 || NET47 || NET471 || NET472 || NET48
+
+using System.ComponentModel;
+
+// ReSharper disable once CheckNamespace
+namespace System.Runtime.CompilerServices
+{
+ ///
+ /// Reserved to be used by the compiler for tracking metadata.
+ /// This class should not be used by developers in source code.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ internal static class IsExternalInit
+ {
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/OpenRGB.NET/Models/Color.cs b/src/OpenRGB.NET/Models/Color.cs
index 6f36ebf..d8becda 100644
--- a/src/OpenRGB.NET/Models/Color.cs
+++ b/src/OpenRGB.NET/Models/Color.cs
@@ -1,5 +1,4 @@
-using System;
-using OpenRGB.NET.Utils;
+using System.Runtime.InteropServices;
namespace OpenRGB.NET;
@@ -9,6 +8,7 @@ namespace OpenRGB.NET;
/// The Red component
/// The Green component
/// The Blue component
+[StructLayout(LayoutKind.Sequential, Pack = 1)]
public readonly record struct Color(byte R = 0, byte G = 0, byte B = 0)
{
///
@@ -26,37 +26,6 @@ public readonly record struct Color(byte R = 0, byte G = 0, byte B = 0)
///
public byte B { get; } = B;
- private static Color ReadFrom(ref SpanReader reader)
- {
- var r = reader.ReadByte();
- var g = reader.ReadByte();
- var b = reader.ReadByte();
- var a = reader.ReadByte();
-
- return new Color(r, g, b);
- }
-
- internal static Color[] ReadManyFrom(ref SpanReader reader, int count)
- {
- if (count < 0)
- throw new ArgumentOutOfRangeException(nameof(count), "Count must be greater than or equal to 0.");
-
- if (count == 0)
- return Array.Empty();
-
- var colors = new Color[count];
-
- for (var i = 0; i < count; i++)
- colors[i] = ReadFrom(ref reader);
-
- return colors;
- }
-
- internal void WriteTo(ref SpanWriter writer)
- {
- writer.WriteByte(R);
- writer.WriteByte(G);
- writer.WriteByte(B);
- writer.WriteByte(0);
- }
+ //Added for padding, so Color fits within 4 bytes.
+ private readonly byte UnusedAlpha = 0;
}
\ No newline at end of file
diff --git a/src/OpenRGB.NET/Models/Device.cs b/src/OpenRGB.NET/Models/Device.cs
index 5212415..eedb557 100644
--- a/src/OpenRGB.NET/Models/Device.cs
+++ b/src/OpenRGB.NET/Models/Device.cs
@@ -1,6 +1,3 @@
-using System;
-using OpenRGB.NET.Utils;
-
namespace OpenRGB.NET;
///
@@ -8,7 +5,7 @@ namespace OpenRGB.NET;
///
public class Device
{
- private Device(int index, DeviceType type, string name, string? vendor,
+ internal Device(int index, DeviceType type, string name, string? vendor,
string description, string version, string serial, string location, int activeModeIndex,
Mode[] modes, Zone[] zones, Led[] leds, Color[] colors)
{
@@ -97,35 +94,9 @@ private Device(int index, DeviceType type, string name, string? vendor,
///
public Mode ActiveMode => Modes[ActiveModeIndex];
- internal static Device ReadFrom(ref SpanReader reader, ProtocolVersion protocol, int deviceIndex)
- {
- var duplicatePacketLength = reader.ReadUInt32();
-
- var deviceType = reader.ReadInt32();
- var name = reader.ReadLengthAndString();
- var vendor = protocol.SupportsVendorString ? reader.ReadLengthAndString() : null;
- var description = reader.ReadLengthAndString();
- var version = reader.ReadLengthAndString();
- var serial = reader.ReadLengthAndString();
- var location = reader.ReadLengthAndString();
- var modeCount = reader.ReadUInt16();
- var activeMode = reader.ReadInt32();
- var modes = Mode.ReadManyFrom(ref reader, modeCount, protocol);
- var zoneCount = reader.ReadUInt16();
- var zones = Zone.ReadManyFrom(ref reader, zoneCount, deviceIndex, protocol);
- var ledCount = reader.ReadUInt16();
- var leds = Led.ReadManyFrom(ref reader, ledCount);
- var colorCount = reader.ReadUInt16();
- var colors = Color.ReadManyFrom(ref reader, colorCount);
-
- return new Device(deviceIndex, (DeviceType)deviceType,
- name, vendor, description, version, serial, location,
- activeMode, modes, zones, leds, colors);
- }
-
///
public override string ToString()
{
return $"{Type}: {Name}";
}
-}
\ No newline at end of file
+}
diff --git a/src/OpenRGB.NET/Models/Led.cs b/src/OpenRGB.NET/Models/Led.cs
index dc29d50..84f8338 100644
--- a/src/OpenRGB.NET/Models/Led.cs
+++ b/src/OpenRGB.NET/Models/Led.cs
@@ -1,5 +1,3 @@
-using OpenRGB.NET.Utils;
-
namespace OpenRGB.NET;
///
@@ -7,7 +5,7 @@ namespace OpenRGB.NET;
///
public class Led
{
- private Led(int index, string name, uint value)
+ internal Led(int index, string name, uint value)
{
Index = index;
Name = name;
@@ -29,30 +27,6 @@ private Led(int index, string name, uint value)
///
public uint Value { get; }
- private static Led ReadFrom(ref SpanReader reader, int index)
- {
- var name = reader.ReadLengthAndString();
- var value = reader.ReadUInt32();
-
- return new Led(index, name, value);
- }
-
- ///
- /// Decodes a byte array into a LED array.
- /// Increments the offset accordingly.
- ///
- ///
- ///
- internal static Led[] ReadManyFrom(ref SpanReader reader, ushort ledCount)
- {
- var leds = new Led[ledCount];
-
- for (var i = 0; i < ledCount; i++)
- leds[i] = ReadFrom(ref reader, i);
-
- return leds;
- }
-
///
public override string ToString()
{
diff --git a/src/OpenRGB.NET/Models/MatrixMap.cs b/src/OpenRGB.NET/Models/MatrixMap.cs
index 23766e7..6909b17 100644
--- a/src/OpenRGB.NET/Models/MatrixMap.cs
+++ b/src/OpenRGB.NET/Models/MatrixMap.cs
@@ -1,5 +1,3 @@
-using OpenRGB.NET.Utils;
-
namespace OpenRGB.NET;
///
@@ -7,7 +5,7 @@ namespace OpenRGB.NET;
///
public class MatrixMap
{
- private MatrixMap(uint height, uint width, uint[,] matrix)
+ internal MatrixMap(uint height, uint width, uint[,] matrix)
{
Height = height;
Width = width;
@@ -28,35 +26,4 @@ private MatrixMap(uint height, uint width, uint[,] matrix)
/// The matrix.
///
public uint[,] Matrix { get; }
-
- internal uint Length => Height * Width * 4 + 8;
-
- ///
- /// Decodes a byte array into a matrix map
- ///
- ///
- internal static MatrixMap ReadFrom(ref SpanReader reader)
- {
- var height = reader.ReadUInt32();
-
- var width = reader.ReadUInt32();
-
- var matrix = new uint[height, width];
-
- for (var i = 0; i < height; i++)
- for (var j = 0; j < width; j++)
- matrix[i, j] = reader.ReadUInt32();
-
- return new MatrixMap(height, width, matrix);
- }
-
- internal void WriteTo(ref SpanWriter writer)
- {
- writer.WriteUInt32(Height);
- writer.WriteUInt32(Width);
-
- for (var i = 0; i < Height; i++)
- for (var j = 0; j < Width; j++)
- writer.WriteUInt32(Matrix[i, j]);
- }
}
\ No newline at end of file
diff --git a/src/OpenRGB.NET/Models/Mode.cs b/src/OpenRGB.NET/Models/Mode.cs
index 08ac4ed..d4e4099 100644
--- a/src/OpenRGB.NET/Models/Mode.cs
+++ b/src/OpenRGB.NET/Models/Mode.cs
@@ -1,5 +1,4 @@
using System;
-using OpenRGB.NET.Utils;
namespace OpenRGB.NET;
@@ -8,7 +7,7 @@ namespace OpenRGB.NET;
///
public class Mode
{
- private Mode(ProtocolVersion protocolVersion, int index, string name, int value, ModeFlags flags, uint speedMin,
+ internal Mode(ProtocolVersion protocolVersion, int index, string name, int value, ModeFlags flags, uint speedMin,
uint speedMax, uint brightnessMin, uint brightnessMax, uint colorMin, uint colorMax, uint speed,
uint brightness, Direction direction, ColorMode colorMode, Color[] colors)
{
@@ -165,80 +164,4 @@ public void SetColors(Color[] newColors)
{
Colors = newColors;
}
-
- internal static Mode ReadFrom(ref SpanReader reader, ProtocolVersion protocolVersion, int index)
- {
- var name = reader.ReadLengthAndString();
- var modeValue = reader.ReadInt32();
- var modeFlags = (ModeFlags)reader.ReadUInt32();
- var speedMin = reader.ReadUInt32();
- var speedMax = reader.ReadUInt32();
- var brightMin = protocolVersion.SupportsBrightnessAndSaveMode ? reader.ReadUInt32() : 0;
- var brightMax = protocolVersion.SupportsBrightnessAndSaveMode ? reader.ReadUInt32() : 0;
- var colorMin = reader.ReadUInt32();
- var colorMax = reader.ReadUInt32();
- var speed = reader.ReadUInt32();
- var brightness = protocolVersion.SupportsBrightnessAndSaveMode ? reader.ReadUInt32() : 0;
- var direction = reader.ReadUInt32();
- var colorMode = (ColorMode)reader.ReadUInt32();
- var colorCount = reader.ReadUInt16();
- var colors = Color.ReadManyFrom(ref reader, colorCount);
-
- return new Mode(protocolVersion, index, name, modeValue, modeFlags, speedMin, speedMax,
- brightMin, brightMax, colorMin, colorMax, speed, brightness,
- (Direction)direction, colorMode, colors);
- }
-
- internal static Mode[] ReadManyFrom(ref SpanReader reader, ushort numModes, ProtocolVersion protocolVersion)
- {
- var modes = new Mode[numModes];
-
- for (var i = 0; i < numModes; i++)
- modes[i] = ReadFrom(ref reader, protocolVersion, i);
-
- return modes;
- }
-
- internal void WriteTo(ref SpanWriter writer)
- {
- writer.WriteLengthAndString(Name);
- writer.WriteInt32(Value);
- writer.WriteUInt32((uint)Flags);
- writer.WriteUInt32(SpeedMin);
- writer.WriteUInt32(SpeedMax);
-
- if (ProtocolVersion.SupportsBrightnessAndSaveMode)
- {
- writer.WriteUInt32(BrightnessMin);
- writer.WriteUInt32(BrightnessMax);
- }
-
- writer.WriteUInt32(ColorMin);
- writer.WriteUInt32(ColorMax);
- writer.WriteUInt32(Speed);
-
- if (ProtocolVersion.SupportsBrightnessAndSaveMode)
- writer.WriteUInt32(Brightness);
-
- writer.WriteUInt32((uint)Direction);
- writer.WriteUInt32((uint)ColorMode);
- writer.WriteUInt16((ushort)Colors.Length);
-
- foreach (var color in Colors)
- color.WriteTo(ref writer);
- }
-
- internal int GetLength()
- {
- var size = (
- sizeof(int) * 2 +
- sizeof(uint) * 9 +
- sizeof(ushort) * 2 +
- sizeof(uint) * Colors.Length +
- Name.Length + 1);
-
- if (ProtocolVersion.SupportsBrightnessAndSaveMode) size += sizeof(uint) * 3;
-
- return size;
- }
}
\ No newline at end of file
diff --git a/src/OpenRGB.NET/Models/PacketHeader.cs b/src/OpenRGB.NET/Models/PacketHeader.cs
deleted file mode 100644
index 3a32d77..0000000
--- a/src/OpenRGB.NET/Models/PacketHeader.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-using System;
-using System.Text;
-using OpenRGB.NET.Utils;
-
-namespace OpenRGB.NET;
-
-///
-/// Packet Header class containing the command ID and the length of the data to be sent.
-///
-internal readonly struct PacketHeader
-{
- internal const int Length = 16;
- private const string Magic = "ORGB";
- internal static readonly byte[] MagicBytes = Encoding.ASCII.GetBytes(Magic);
- internal uint DeviceId { get; }
- internal CommandId Command { get; }
- internal uint DataLength { get; }
-
- internal PacketHeader(uint deviceId, CommandId command, uint length)
- {
- DeviceId = deviceId;
- Command = command;
- DataLength = length;
- }
-
- internal static PacketHeader ReadFrom(ref SpanReader reader)
- {
- if (!reader.ReadBytes(4).SequenceEqual(MagicBytes))
- throw new ArgumentException($"Magic bytes \"{Magic}\" were not found. Data was {reader.Span.ToArray()}");
-
- return new PacketHeader(reader.ReadUInt32(), (CommandId)reader.ReadUInt32(), reader.ReadUInt32());
- }
-
- internal void WriteTo(ref SpanWriter writer)
- {
- writer.WriteBytes(MagicBytes);
- writer.WriteUInt32(DeviceId);
- writer.WriteUInt32((uint)Command);
- writer.WriteUInt32(DataLength);
- }
-}
diff --git a/src/OpenRGB.NET/Models/Plugin.cs b/src/OpenRGB.NET/Models/Plugin.cs
index 40ad08d..f5597e0 100644
--- a/src/OpenRGB.NET/Models/Plugin.cs
+++ b/src/OpenRGB.NET/Models/Plugin.cs
@@ -1,13 +1,11 @@
-using OpenRGB.NET.Utils;
-
-namespace OpenRGB.NET;
+namespace OpenRGB.NET;
///
/// Represents a Plugin installed on the OpenRGB server.
///
public class Plugin
{
- private Plugin(string name, string description, string version, uint index, int sdkVersion)
+ internal Plugin(string name, string description, string version, uint index, int sdkVersion)
{
Name = name;
Description = description;
@@ -40,25 +38,4 @@ private Plugin(string name, string description, string version, uint index, int
/// The SDK version of the plugin.
///
public int SdkVersion { get; }
-
- private static Plugin ReadFrom(ref SpanReader reader)
- {
- var name = reader.ReadLengthAndString();
- var description = reader.ReadLengthAndString();
- var version = reader.ReadLengthAndString();
- var index = reader.ReadUInt32();
- var sdkVersion = reader.ReadInt32();
-
- return new Plugin(name, description, version, index, sdkVersion);
- }
-
- internal static Plugin[] ReadManyFrom(ref SpanReader reader, ushort count)
- {
- var plugins = new Plugin[count];
-
- for (var i = 0; i < count; i++)
- plugins[i] = ReadFrom(ref reader);
-
- return plugins;
- }
}
\ No newline at end of file
diff --git a/src/OpenRGB.NET/Models/ProtocolVersion.cs b/src/OpenRGB.NET/Models/ProtocolVersion.cs
index 47b4e9d..1965598 100644
--- a/src/OpenRGB.NET/Models/ProtocolVersion.cs
+++ b/src/OpenRGB.NET/Models/ProtocolVersion.cs
@@ -85,4 +85,4 @@ private ProtocolVersion(uint number,
4 => V4,
_ => throw new ArgumentOutOfRangeException(nameof(number), number, "Unknown protocol version")
};
-}
\ No newline at end of file
+}
diff --git a/src/OpenRGB.NET/Models/Segment.cs b/src/OpenRGB.NET/Models/Segment.cs
index 0140abb..b78298c 100644
--- a/src/OpenRGB.NET/Models/Segment.cs
+++ b/src/OpenRGB.NET/Models/Segment.cs
@@ -1,5 +1,3 @@
-using OpenRGB.NET.Utils;
-
namespace OpenRGB.NET;
///
@@ -7,7 +5,7 @@ namespace OpenRGB.NET;
///
public class Segment
{
- private Segment(int index, string name, ZoneType type, uint start, uint ledCount)
+ internal Segment(int index, string name, ZoneType type, uint start, uint ledCount)
{
Index = index;
Name = name;
@@ -40,24 +38,4 @@ private Segment(int index, string name, ZoneType type, uint start, uint ledCount
/// The number of LEDs in the segment.
///
public uint LedCount { get; }
-
- private static Segment ReadFrom(ref SpanReader reader, int index)
- {
- var name = reader.ReadLengthAndString();
- var type = (ZoneType)reader.ReadUInt32();
- var start = reader.ReadUInt32();
- var ledCount = reader.ReadUInt32();
-
- return new Segment(index, name, type, start, ledCount);
- }
-
- internal static Segment[] ReadManyFrom(ref SpanReader reader, ushort segmentCount)
- {
- var segments = new Segment[segmentCount];
-
- for (var i = 0; i < segmentCount; i++)
- segments[i] = ReadFrom(ref reader, i);
-
- return segments;
- }
}
\ No newline at end of file
diff --git a/src/OpenRGB.NET/Models/Zone.cs b/src/OpenRGB.NET/Models/Zone.cs
index 44954d2..7f59af2 100644
--- a/src/OpenRGB.NET/Models/Zone.cs
+++ b/src/OpenRGB.NET/Models/Zone.cs
@@ -1,6 +1,3 @@
-using System;
-using OpenRGB.NET.Utils;
-
namespace OpenRGB.NET;
///
@@ -8,7 +5,7 @@ namespace OpenRGB.NET;
///
public class Zone
{
- private Zone(int index, int deviceIndex, string name, ZoneType type, uint ledCount, uint ledsMin, uint ledsMax, MatrixMap? matrixMap, Segment[] segments)
+ internal Zone(int index, int deviceIndex, string name, ZoneType type, uint ledCount, uint ledsMin, uint ledsMax, MatrixMap? matrixMap, Segment[] segments)
{
Index = index;
DeviceIndex = deviceIndex;
@@ -65,39 +62,4 @@ private Zone(int index, int deviceIndex, string name, ZoneType type, uint ledCou
/// A list of segments in the zone. Will be null if protocol version is below 4.
///
public Segment[] Segments { get; }
-
- private static Zone ReadFrom(ref SpanReader reader, int deviceIndex, int zoneIndex, ProtocolVersion protocolVersion)
- {
- var name = reader.ReadLengthAndString();
- var type = (ZoneType)reader.ReadUInt32();
- var ledsMin = reader.ReadUInt32();
- var ledsMax = reader.ReadUInt32();
- var ledCount = reader.ReadUInt32();
- var zoneMatrixLength = reader.ReadUInt16();
- var matrixMap = zoneMatrixLength > 0 ? MatrixMap.ReadFrom(ref reader) : null;
- var segments = protocolVersion.SupportsSegmentsAndPlugins ? Segment.ReadManyFrom(ref reader, reader.ReadUInt16()) : Array.Empty();
-
- return new Zone(zoneIndex, deviceIndex, name, type, ledCount, ledsMin, ledsMax, matrixMap, segments);
- }
-
- internal static Zone[] ReadManyFrom(ref SpanReader reader, ushort zoneCount, int deviceID, ProtocolVersion protocolVersion)
- {
- var zones = new Zone[zoneCount];
-
- for (var i = 0; i < zoneCount; i++)
- zones[i] = ReadFrom(ref reader, deviceID, i, protocolVersion);
-
- return zones;
- }
-
- internal void WriteTo(ref SpanWriter writer)
- {
- writer.WriteLengthAndString(Name);
- writer.WriteUInt32((uint)Type);
- writer.WriteUInt32(LedsMin);
- writer.WriteUInt32(LedsMax);
- writer.WriteUInt32(LedCount);
- writer.WriteUInt16((ushort)(MatrixMap?.Length ?? 0));
- MatrixMap?.WriteTo(ref writer);
- }
}
\ No newline at end of file
diff --git a/src/OpenRGB.NET/OpenRGB.NET.csproj b/src/OpenRGB.NET/OpenRGB.NET.csproj
index df69d01..8f8b032 100644
--- a/src/OpenRGB.NET/OpenRGB.NET.csproj
+++ b/src/OpenRGB.NET/OpenRGB.NET.csproj
@@ -1,8 +1,8 @@
- netstandard2.1
- 12
+ net8.0
+ latest
enable
3.0.1
true
diff --git a/src/OpenRGB.NET/OpenRGBClient.cs b/src/OpenRGB.NET/OpenRGBClient.cs
index f575ea2..4c3cc3e 100644
--- a/src/OpenRGB.NET/OpenRGBClient.cs
+++ b/src/OpenRGB.NET/OpenRGBClient.cs
@@ -1,13 +1,5 @@
using System;
-using System.Buffers;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Net.Sockets;
-using System.Threading;
-using System.Threading.Tasks;
-using OpenRGB.NET.Utils;
+using System.Runtime.InteropServices;
namespace OpenRGB.NET;
@@ -21,23 +13,20 @@ public sealed class OpenRgbClient : IDisposable, IOpenRgbClient
private readonly string _ip;
private readonly int _port;
private readonly int _timeoutMs;
- private readonly Socket _socket;
- private readonly byte[] _headerBuffer;
- private Task? _readLoopTask;
- private readonly CancellationTokenSource _cancellationTokenSource;
- private readonly Dictionary> _pendingRequests;
+ private readonly uint _protocolVersionNumber;
+ private readonly OpenRgbConnection _connection;
///
- public bool Connected => _socket.Connected;
+ public bool Connected => _connection.Connected;
///
public ProtocolVersion MaxSupportedProtocolVersion => ProtocolVersion.FromNumber(MaxProtocolNumber);
-
+
///
- public ProtocolVersion ClientProtocolVersion { get; }
+ public ProtocolVersion ClientProtocolVersion => ProtocolVersion.FromNumber(_protocolVersionNumber);
///
- public ProtocolVersion CommonProtocolVersion { get; private set; }
+ public ProtocolVersion CommonProtocolVersion => _connection.CurrentProtocolVersion;
///
public event EventHandler? DeviceListUpdated;
@@ -57,19 +46,13 @@ public OpenRgbClient(string ip = "127.0.0.1",
_port = port;
_name = name;
_timeoutMs = timeoutMs;
- _cancellationTokenSource = new CancellationTokenSource();
- _pendingRequests = Enum.GetValues(typeof(CommandId)).Cast().ToDictionary(c => c, _ => new BlockingCollection());
- _headerBuffer = new byte[PacketHeader.Length];
- _socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
- _socket.NoDelay = true;
+ _protocolVersionNumber = protocolVersionNumber;
+ _connection = new OpenRgbConnection(DeviceListUpdated);
if (protocolVersionNumber > MaxProtocolNumber)
throw new ArgumentException("Client protocol version provided higher than supported.",
nameof(protocolVersionNumber));
- CommonProtocolVersion = ProtocolVersion.Invalid;
- ClientProtocolVersion = ProtocolVersion.FromNumber(protocolVersionNumber);
-
if (autoConnect) Connect();
}
@@ -79,169 +62,7 @@ public void Connect()
if (Connected)
return;
- _socket.Connect(_ip, _port, _timeoutMs, _cancellationTokenSource.Token);
- _readLoopTask = Task.Run(ReadLoop);
-
- var length = PacketHeader.Length + PacketFactory.GetStringOperationLength(_name);
- var rent = ArrayPool.Shared.Rent(length);
- var packet = rent.AsSpan(0, length);
-
- PacketFactory.WriteStringOperation(packet, _name, CommandId.SetClientName);
-
- try
- {
- SendOrThrow(packet);
- }
- finally
- {
- ArrayPool.Shared.Return(rent);
- }
-
- var minimumCommonVersionNumber = Math.Min(ClientProtocolVersion.Number, GetServerProtocolVersion());
- CommonProtocolVersion = ProtocolVersion.FromNumber(minimumCommonVersionNumber);
- }
-
- private async Task ReadLoop()
- {
- while (!_cancellationTokenSource.IsCancellationRequested && Connected)
- {
- try
- {
- //todo: handle zero
- await _socket.ReceiveAsync(_headerBuffer, SocketFlags.None, _cancellationTokenSource.Token);
-
- var dataLength = ParseHeader();
- if (dataLength.Command == CommandId.DeviceListUpdated)
- {
- //TODO: is this the best way to do this?
- DeviceListUpdated?.Invoke(this, EventArgs.Empty);
- }
- else
- {
- var dataBuffer = new byte[dataLength.DataLength];
- await _socket.ReceiveAsync(dataBuffer, SocketFlags.None, _cancellationTokenSource.Token);
- _pendingRequests[dataLength.Command].Add(dataBuffer);
- }
- }
- catch (TaskCanceledException)
- {
- //ignore
- }
- }
- }
-
- private PacketHeader ParseHeader()
- {
- var reader = new SpanReader(_headerBuffer);
- return PacketHeader.ReadFrom(ref reader);
- }
-
- private void SendHeader(CommandId command, uint deviceId)
- {
- Span packet = stackalloc byte[PacketHeader.Length];
- var writer = new SpanWriter(packet);
-
- var header = new PacketHeader(deviceId, command, 0);
- header.WriteTo(ref writer);
-
- SendOrThrow(packet);
- }
-
- private byte[] SendHeaderAndGetResponse(CommandId command, uint deviceId)
- {
- SendHeader(command, deviceId);
-
- return GetResponse(command);
- }
-
- private byte[] GetResponse(CommandId command)
- {
- if (!_pendingRequests[command].TryTake(out var outBuffer, _timeoutMs))
- throw new TimeoutException($"Did not receive response to {command} in expected time of {_timeoutMs} ms");
-
- return outBuffer;
- }
-
- private void SendOrThrow(Span buffer)
- {
- var size = buffer.Length;
- var total = 0;
-
- while (total < size)
- {
- var recv = _socket.Send(buffer[total..]);
- if (recv == 0 && total != size)
- {
- throw new IOException("Sent incorrect number of bytes.");
- }
-
- total += recv;
- }
- }
-
- private uint GetServerProtocolVersion()
- {
- uint serverVersion;
-
- _socket.ReceiveTimeout = 1000;
-
- Span packet = stackalloc byte[PacketHeader.Length + PacketFactory.ProtocolVersionLength];
- PacketFactory.WriteProtocolVersion(packet, 0, ClientProtocolVersion.Number, CommandId.RequestProtocolVersion);
-
- try
- {
- SendOrThrow(packet);
-
- var response = GetResponse(CommandId.RequestProtocolVersion);
- serverVersion = BitConverter.ToUInt32(response);
- }
- catch (SocketException e) when (e.SocketErrorCode == SocketError.TimedOut)
- {
- serverVersion = 0;
- }
-
- _socket.ReceiveTimeout = 0;
-
- return serverVersion;
- }
-
- private void ProfileOperation(string profile, CommandId operation)
- {
- if (!CommonProtocolVersion.SupportsProfileControls)
- throw new NotSupportedException($"Not supported on protocol version {ClientProtocolVersion}");
-
- var length = PacketHeader.Length + PacketFactory.GetStringOperationLength(profile);
- var rent = ArrayPool.Shared.Rent(length);
- var packet = rent.AsSpan(0, length);
-
- PacketFactory.WriteStringOperation(packet, profile, operation);
-
- try
- {
- SendOrThrow(packet);
- }
- finally
- {
- ArrayPool.Shared.Return(rent);
- }
- }
-
- private void ModeOperation(int deviceId, int modeId, Mode targetMode, CommandId operation)
- {
- var length = PacketHeader.Length + PacketFactory.GetModeOperationLength(targetMode);
- var rent = ArrayPool.Shared.Rent(length);
- var packet = rent.AsSpan(0, length);
-
- PacketFactory.WriteModeOperation(packet, (uint)deviceId, (uint)modeId, targetMode, operation);
-
- try
- {
- SendOrThrow(packet);
- }
- finally
- {
- ArrayPool.Shared.Return(rent);
- }
+ _connection.Connect(_name, _ip, _port, _timeoutMs, _protocolVersionNumber);
}
#region API
@@ -249,7 +70,7 @@ private void ModeOperation(int deviceId, int modeId, Mode targetMode, CommandId
///
public int GetControllerCount()
{
- return (int)BitConverter.ToUInt32(SendHeaderAndGetResponse(CommandId.RequestControllerCount, 0), 0);
+ return _connection.Request, int>(CommandId.RequestControllerCount, 0, new EmptyArg());
}
///
@@ -258,15 +79,8 @@ public Device GetControllerData(int deviceId)
if (deviceId < 0)
throw new ArgumentException("Unexpected device Id", nameof(deviceId));
- Span packet = stackalloc byte[PacketHeader.Length + PacketFactory.ProtocolVersionLength];
-
- PacketFactory.WriteProtocolVersion(packet, (uint)deviceId, CommonProtocolVersion.Number, CommandId.RequestControllerData);
-
- SendOrThrow(packet);
-
- var response = GetResponse(CommandId.RequestControllerData);
- var responseReader = new SpanReader(response);
- return Device.ReadFrom(ref responseReader, CommonProtocolVersion, deviceId);
+ return _connection.Request(CommandId.RequestControllerData, (uint)deviceId,
+ new ProtocolVersionArg(_connection.CurrentProtocolVersion));
}
///
@@ -285,49 +99,24 @@ public Device[] GetAllControllerData()
public string[] GetProfiles()
{
if (!CommonProtocolVersion.SupportsProfileControls)
- throw new NotSupportedException($"Not supported on protocol version {ClientProtocolVersion}");
-
- Span packet = stackalloc byte[PacketHeader.Length + PacketFactory.ProtocolVersionLength];
-
- PacketFactory.WriteProtocolVersion(packet, 0, CommonProtocolVersion.Number, CommandId.RequestProfiles);
-
- SendOrThrow(packet);
-
- var buffer = GetResponse(CommandId.RequestProfiles);
-
- var reader = new SpanReader(buffer);
- var dataSize = reader.ReadUInt32();
- var count = reader.ReadUInt16();
- var profiles = new string[count];
-
- for (var i = 0; i < count; i++)
- profiles[i] = reader.ReadLengthAndString();
+ throw new NotSupportedException($"Not supported on protocol version {CommonProtocolVersion.Number}");
- return profiles;
+ return _connection.Request(CommandId.RequestProfiles, 0, new EmptyArg());
}
///
public Plugin[] GetPlugins()
{
if (!CommonProtocolVersion.SupportsSegmentsAndPlugins)
- throw new NotSupportedException($"Not supported on protocol version {ClientProtocolVersion.Number}");
+ throw new NotSupportedException($"Not supported on protocol version {CommonProtocolVersion.Number}");
- var buffer = SendHeaderAndGetResponse(CommandId.RequestPlugins, 0);
- var reader = new SpanReader(buffer);
- var dataSize = reader.ReadUInt32();
- var count = reader.ReadUInt16();
-
- return Plugin.ReadManyFrom(ref reader, count);
+ return _connection.Request(CommandId.RequestPlugins, 0, new EmptyArg());
}
///
public void ResizeZone(int deviceId, int zoneId, int size)
{
- Span packet = stackalloc byte[PacketFactory.ResizeZoneLength];
-
- PacketFactory.WriteResizeZone(packet, (uint)deviceId, (uint)zoneId, (uint)size);
-
- SendOrThrow(packet);
+ _connection.Send(CommandId.ResizeZone, (uint)deviceId, new Args((uint)zoneId, (uint)size));
}
///
@@ -339,20 +128,9 @@ public void UpdateLeds(int deviceId, ReadOnlySpan colors)
if (deviceId < 0)
throw new ArgumentException("Invalid deviceId", nameof(deviceId));
- var length = PacketHeader.Length + PacketFactory.GetUpdateLedsLength(colors.Length);
- var rent = ArrayPool.Shared.Rent(length);
- var packet = rent.AsSpan(0, length);
-
- PacketFactory.WriteUpdateLeds(packet, deviceId, colors);
+ var bytes = MemoryMarshal.Cast(colors);
- try
- {
- SendOrThrow(packet);
- }
- finally
- {
- ArrayPool.Shared.Return(rent);
- }
+ _connection.Send(CommandId.UpdateLeds, (uint)deviceId, new Args(0, (ushort)colors.Length), bytes);
}
///
@@ -367,20 +145,9 @@ public void UpdateZoneLeds(int deviceId, int zoneId, ReadOnlySpan colors)
if (zoneId < 0)
throw new ArgumentException("Invalid zone id", nameof(zoneId));
- var length = PacketHeader.Length + PacketFactory.GetUpdateZoneLedsLength(colors.Length);
- var rent = ArrayPool.Shared.Rent(length);
- var packet = rent.AsSpan(0, length);
-
- PacketFactory.WriteUpdateZoneLeds(packet, (uint)deviceId, (uint)zoneId, colors);
+ var bytes = MemoryMarshal.Cast(colors);
- try
- {
- SendOrThrow(packet);
- }
- finally
- {
- ArrayPool.Shared.Return(rent);
- }
+ _connection.Send(CommandId.UpdateZoneLeds, (uint)deviceId, new Args(0, (uint)zoneId, (ushort)colors.Length), bytes);
}
///
@@ -392,35 +159,31 @@ public void UpdateSingleLed(int deviceId, int ledId, Color color)
if (ledId < 0)
throw new ArgumentException("Invalid led id", nameof(ledId));
- Span packet = stackalloc byte[PacketHeader.Length + PacketFactory.UpdateSingleLedLength];
-
- PacketFactory.WriteUpdateSingleLed(packet, (uint)deviceId, (uint)ledId, color);
-
- SendOrThrow(packet);
+ _connection.Send(CommandId.UpdateSingleLed, (uint)deviceId, new Args((uint)ledId, color));
}
///
public void SetCustomMode(int deviceId)
{
- SendHeader(CommandId.SetCustomMode, (uint)deviceId);
+ _connection.Send(CommandId.SetCustomMode, (uint)deviceId, new EmptyArg());
}
///
public void LoadProfile(string profile)
{
- ProfileOperation(profile, CommandId.LoadProfile);
+ _connection.Send(CommandId.LoadProfile, 0, new StringArg(profile));
}
///
public void SaveProfile(string profile)
{
- ProfileOperation(profile, CommandId.SaveProfile);
+ _connection.Send(CommandId.SaveProfile, 0, new StringArg(profile));
}
///
public void DeleteProfile(string profile)
{
- ProfileOperation(profile, CommandId.DeleteProfile);
+ _connection.Send(CommandId.DeleteProfile, 0, new StringArg(profile));
}
///
@@ -460,7 +223,7 @@ public void UpdateMode(int deviceId, int modeId,
targetMode.SetColors(colors);
}
- ModeOperation(deviceId, modeId, targetMode, CommandId.UpdateMode);
+ _connection.Send(CommandId.UpdateMode, (uint)deviceId, new ModeOperationArg(0, (uint)modeId, new ModeArg(targetMode)));
}
///
@@ -473,29 +236,16 @@ public void SaveMode(int deviceId, int modeId)
var targetMode = targetDevice.Modes[modeId];
- ModeOperation(deviceId, modeId, targetMode, CommandId.SaveMode);
+ _connection.Send(CommandId.SaveMode, (uint)deviceId, new ModeOperationArg(0, (uint)modeId, new ModeArg(targetMode)));
}
///
public void PluginSpecific(int pluginId, int pluginPacketType, ReadOnlySpan data)
{
if (!CommonProtocolVersion.SupportsSegmentsAndPlugins)
- throw new NotSupportedException($"Not supported on protocol version {ClientProtocolVersion.Number}");
-
- var length = PacketHeader.Length + PacketFactory.GetPluginSpecificLength(data);
- var rent = ArrayPool.Shared.Rent(length);
- var packet = rent.AsSpan(0, length);
+ throw new NotSupportedException($"Not supported on protocol version {CommonProtocolVersion.Number}");
- PacketFactory.WritePluginSpecific(packet, (uint)pluginPacketType, (uint)pluginId, data);
-
- try
- {
- SendOrThrow(packet);
- }
- finally
- {
- ArrayPool.Shared.Return(rent);
- }
+ _connection.Send(CommandId.PluginSpecific, (uint)pluginId, new Args((uint)pluginPacketType), data);
}
#endregion
@@ -503,18 +253,6 @@ public void PluginSpecific(int pluginId, int pluginPacketType, ReadOnlySpan
public void Dispose()
{
- _cancellationTokenSource.Cancel();
- _cancellationTokenSource.Dispose();
-
- try
- {
- _readLoopTask?.Wait();
- }
- catch
- {
- // ignored
- }
- _socket.Dispose();
-
+ _connection.Dispose();
}
}
\ No newline at end of file
diff --git a/src/OpenRGB.NET/OpenRgbConnection.cs b/src/OpenRGB.NET/OpenRgbConnection.cs
new file mode 100644
index 0000000..7e97f2e
--- /dev/null
+++ b/src/OpenRGB.NET/OpenRgbConnection.cs
@@ -0,0 +1,187 @@
+using System;
+using System.Buffers;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Net.Sockets;
+using System.Threading;
+using System.Threading.Tasks;
+using OpenRGB.NET.Utils;
+
+namespace OpenRGB.NET;
+
+internal sealed class OpenRgbConnection : IDisposable
+{
+ private readonly CancellationTokenSource _cancellationTokenSource;
+ private readonly Socket _socket;
+ private readonly Dictionary> _pendingRequests;
+ private Task? _readLoopTask;
+
+ public bool Connected => _socket.Connected;
+
+ public ProtocolVersion CurrentProtocolVersion { get; private set; }
+
+ public EventHandler? DeviceListUpdated { get; set; }
+
+ public OpenRgbConnection(EventHandler? OnDeviceListUpdated)
+ {
+ _cancellationTokenSource = new CancellationTokenSource();
+ _socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
+ _socket.NoDelay = true; //Send all data immediately, we rely on the order of packets
+ _pendingRequests = Enum.GetValues(typeof(CommandId)).Cast()
+ .ToDictionary(c => c, _ => new BlockingCollection());
+
+ DeviceListUpdated = OnDeviceListUpdated;
+ }
+
+ public void Connect(string name, string ip, int port, int timeoutMs, uint protocolVersionNumber = 4)
+ {
+ if (Connected)
+ return;
+
+ _socket.Connect(ip, port, timeoutMs, _cancellationTokenSource.Token);
+ _readLoopTask = Task.Run(ReadLoop, _cancellationTokenSource.Token);
+
+ Send(CommandId.SetClientName, 0, new StringArg(name));
+
+ var commonProtocolVersion = NegotiateProtocolVersion(protocolVersionNumber);
+ CurrentProtocolVersion = ProtocolVersion.FromNumber(commonProtocolVersion);
+ }
+
+ private async Task ReadLoop()
+ {
+ var headerBuffer = new byte[PacketHeader.LENGTH];
+
+ while (!_cancellationTokenSource.IsCancellationRequested && _socket.Connected)
+ {
+ try
+ {
+ await _socket.ReceiveAllAsync(headerBuffer, _cancellationTokenSource.Token);
+ var header = PacketHeader.FromSpan(headerBuffer);
+
+ if (header.Command == CommandId.DeviceListUpdated)
+ {
+ _ = Task.Run(() =>
+ {
+ try
+ {
+ DeviceListUpdated?.Invoke(this, EventArgs.Empty);
+ }
+ catch
+ {
+ //ignored
+ }
+ });
+ }
+ else
+ {
+ var dataBuffer = new byte[header.DataLength];
+ await _socket.ReceiveAllAsync(dataBuffer, _cancellationTokenSource.Token);
+ DebugDumpBuffer(dataBuffer, false, header.Command);
+ _pendingRequests[header.Command].Add(dataBuffer, _cancellationTokenSource.Token);
+ }
+ }
+ catch (TaskCanceledException)
+ {
+ //ignore
+ }
+ }
+ }
+
+
+ public void Send(CommandId command, uint deviceId, TRequest requestData, ReadOnlySpan additionalData = default)
+ where TRequest : ISpanWritable
+ {
+ var dataLength = requestData.Length + additionalData.Length;
+ var totalLength = PacketHeader.LENGTH + dataLength;
+ var header = new PacketHeader(deviceId, command, (uint)dataLength);
+
+ var rent = ArrayPool.Shared.Rent(totalLength);
+ var buffer = rent.AsSpan(0, totalLength);
+ var writer = new SpanWriter(buffer);
+
+ header.WriteTo(ref writer);
+ requestData.WriteTo(ref writer);
+ writer.Write(additionalData);
+
+ try
+ {
+ DebugDumpBuffer(buffer, true, command);
+ _socket.SendAll(buffer);
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(rent);
+ }
+ }
+
+ private TResult Receive(CommandId command, uint deviceId) where TReader : struct, ISpanReader
+ {
+ var reader = new SpanReader(_pendingRequests[command].Take(_cancellationTokenSource.Token));
+ //this deviceId here is a bit hacky, it's used because some Models store their own index
+ return TReader.ReadFrom(ref reader, CurrentProtocolVersion, (int)deviceId);
+ }
+
+ public TResult Request(CommandId command, uint deviceId, TArgument requestData, ReadOnlySpan additionalData = default)
+ where TArgument : ISpanWritable
+ where TReader : struct, ISpanReader
+ {
+ Send(command, deviceId, requestData, additionalData);
+ return Receive(command, deviceId);
+ }
+
+ private uint NegotiateProtocolVersion(uint maxSupportedProtocolVersion)
+ {
+ _socket.ReceiveTimeout = 1000;
+
+ uint version;
+
+ try
+ {
+ version = Request, PrimitiveReader, uint>(CommandId.RequestProtocolVersion, 0,
+ new Args(maxSupportedProtocolVersion));
+ }
+ catch (TimeoutException)
+ {
+ version = 0;
+ }
+
+ _socket.ReceiveTimeout = 0;
+
+ return Math.Min(version, maxSupportedProtocolVersion);
+ }
+
+ public void Dispose()
+ {
+ _cancellationTokenSource.Cancel();
+ try
+ {
+ _readLoopTask?.Wait();
+ }
+ catch
+ {
+ //ignored
+ }
+
+ _cancellationTokenSource.Dispose();
+ _socket.Dispose();
+ _readLoopTask?.Dispose();
+ }
+
+ [Conditional("DEBUG")]
+ private static void DebugDumpBuffer(ReadOnlySpan buffer, bool sending, CommandId command)
+ {
+ var directory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "OpenRGB.NET");
+
+ if (!Directory.Exists(directory))
+ Directory.CreateDirectory(directory);
+
+ var lastFileName = Directory.EnumerateFiles(directory).MaxBy(f => f);
+ var lastFileNumber = lastFileName is null ? -1 :
+ int.Parse(Path.GetFileNameWithoutExtension(lastFileName).Split('-')[0]);
+
+ File.WriteAllBytes(Path.Combine(directory, $"{lastFileNumber + 1:D2}-{(sending ? "Send" : "Receive")}-{command}.bin"), buffer.ToArray());
+ }
+}
\ No newline at end of file
diff --git a/src/OpenRGB.NET/Readers/ColorsReader.cs b/src/OpenRGB.NET/Readers/ColorsReader.cs
new file mode 100644
index 0000000..f893aea
--- /dev/null
+++ b/src/OpenRGB.NET/Readers/ColorsReader.cs
@@ -0,0 +1,24 @@
+using OpenRGB.NET.Utils;
+
+namespace OpenRGB.NET;
+
+internal readonly struct ColorsReader : ISpanReader
+{
+ public static Color[] ReadFrom(ref SpanReader reader, ProtocolVersion? protocolVersion = default, int? index = default, int? outerCount = default)
+ {
+ var count = reader.Read();
+ var colors = new Color[count];
+
+ for (var i = 0; i < count; i++)
+ {
+ var r = reader.Read();
+ var g = reader.Read();
+ var b = reader.Read();
+ _ = reader.Read();
+
+ colors[i] = new Color(r, g, b);
+ }
+
+ return colors;
+ }
+}
\ No newline at end of file
diff --git a/src/OpenRGB.NET/Readers/DeviceReader.cs b/src/OpenRGB.NET/Readers/DeviceReader.cs
new file mode 100644
index 0000000..62f823c
--- /dev/null
+++ b/src/OpenRGB.NET/Readers/DeviceReader.cs
@@ -0,0 +1,36 @@
+using System;
+using OpenRGB.NET.Utils;
+
+namespace OpenRGB.NET;
+
+internal readonly struct DeviceReader : ISpanReader
+{
+ public static Device ReadFrom(ref SpanReader reader, ProtocolVersion? protocolVersion = default, int? index = default, int? outerCount = default)
+ {
+ if (protocolVersion is not { } protocol)
+ throw new ArgumentNullException(nameof(protocolVersion));
+ if (index is not { } deviceIndex)
+ throw new ArgumentNullException(nameof(index));
+
+ // ReSharper disable once UnusedVariable
+ var dataSize = reader.Read();
+
+ var deviceType = reader.Read();
+ var name = reader.ReadLengthAndString();
+ var vendor = protocol.SupportsVendorString ? reader.ReadLengthAndString() : null;
+ var description = reader.ReadLengthAndString();
+ var version = reader.ReadLengthAndString();
+ var serial = reader.ReadLengthAndString();
+ var location = reader.ReadLengthAndString();
+ var modeCount = reader.Read();
+ var activeMode = reader.Read();
+ var modes = ModesReader.ReadFrom(ref reader, protocol, outerCount: modeCount);
+ var zones = ZonesReader.ReadFrom(ref reader, protocol, index: deviceIndex);
+ var leds = LedsReader.ReadFrom(ref reader);
+ var colors = ColorsReader.ReadFrom(ref reader);
+
+ return new Device(deviceIndex, (DeviceType)deviceType,
+ name, vendor, description, version, serial, location,
+ activeMode, modes, zones, leds, colors);
+ }
+}
\ No newline at end of file
diff --git a/src/OpenRGB.NET/Readers/LedsReader.cs b/src/OpenRGB.NET/Readers/LedsReader.cs
new file mode 100644
index 0000000..4aba490
--- /dev/null
+++ b/src/OpenRGB.NET/Readers/LedsReader.cs
@@ -0,0 +1,23 @@
+using OpenRGB.NET.Utils;
+
+namespace OpenRGB.NET;
+
+internal readonly struct LedsReader : ISpanReader
+{
+ public static Led[] ReadFrom(ref SpanReader reader, ProtocolVersion? protocolVersion = default, int? index = default, int? outerCount = default)
+ {
+ var ledCount = reader.Read();
+
+ var leds = new Led[ledCount];
+
+ for (var i = 0; i < ledCount; i++)
+ {
+ var name = reader.ReadLengthAndString();
+ var value = reader.Read();
+
+ leds[i] = new Led(i, name, value);
+ }
+
+ return leds;
+ }
+}
\ No newline at end of file
diff --git a/src/OpenRGB.NET/Readers/MatrixMapReader.cs b/src/OpenRGB.NET/Readers/MatrixMapReader.cs
new file mode 100644
index 0000000..891eeaf
--- /dev/null
+++ b/src/OpenRGB.NET/Readers/MatrixMapReader.cs
@@ -0,0 +1,24 @@
+using OpenRGB.NET.Utils;
+
+namespace OpenRGB.NET;
+
+internal readonly struct MatrixMapReader : ISpanReader
+{
+ public static MatrixMap ReadFrom(ref SpanReader reader, ProtocolVersion? protocolVersion = default, int? index = default,
+ int? outerCount = default)
+ {
+ var height = reader.Read();
+ var width = reader.Read();
+ var matrix = new uint[height, width];
+
+ for (var i = 0; i < height; i++)
+ {
+ for (var j = 0; j < width; j++)
+ {
+ matrix[i, j] = reader.Read();
+ }
+ }
+
+ return new MatrixMap(height, width, matrix);
+ }
+}
\ No newline at end of file
diff --git a/src/OpenRGB.NET/Readers/ModesReader.cs b/src/OpenRGB.NET/Readers/ModesReader.cs
new file mode 100644
index 0000000..28cbbee
--- /dev/null
+++ b/src/OpenRGB.NET/Readers/ModesReader.cs
@@ -0,0 +1,41 @@
+using System;
+using OpenRGB.NET.Utils;
+
+namespace OpenRGB.NET;
+
+internal readonly struct ModesReader : ISpanReader
+{
+ public static Mode[] ReadFrom(ref SpanReader reader, ProtocolVersion? protocolVersion = default, int? index = default, int? outerCount = default)
+ {
+ if (protocolVersion is not { } version)
+ throw new ArgumentNullException(nameof(protocolVersion));
+ if (outerCount is not { } count)
+ throw new ArgumentNullException(nameof(outerCount));
+
+ var modes = new Mode[count];
+
+ for (var i = 0; i < modes.Length; i++)
+ {
+ var name = reader.ReadLengthAndString();
+ var modeValue = reader.Read();
+ var modeFlags = (ModeFlags)reader.Read();
+ var speedMin = reader.Read();
+ var speedMax = reader.Read();
+ var brightMin = version.SupportsBrightnessAndSaveMode ? reader.Read() : 0;
+ var brightMax = version.SupportsBrightnessAndSaveMode ? reader.Read() : 0;
+ var colorMin = reader.Read();
+ var colorMax = reader.Read();
+ var speed = reader.Read();
+ var brightness = version.SupportsBrightnessAndSaveMode ? reader.Read() : 0;
+ var direction = reader.Read();
+ var colorMode = (ColorMode)reader.Read();
+ var colors = ColorsReader.ReadFrom(ref reader);
+
+ modes[i] = new Mode(version, i, name, modeValue, modeFlags, speedMin, speedMax,
+ brightMin, brightMax, colorMin, colorMax, speed, brightness,
+ (Direction)direction, colorMode, colors);
+ }
+
+ return modes;
+ }
+}
\ No newline at end of file
diff --git a/src/OpenRGB.NET/Readers/PluginsReader.cs b/src/OpenRGB.NET/Readers/PluginsReader.cs
new file mode 100644
index 0000000..1a21607
--- /dev/null
+++ b/src/OpenRGB.NET/Readers/PluginsReader.cs
@@ -0,0 +1,27 @@
+using OpenRGB.NET.Utils;
+
+namespace OpenRGB.NET;
+
+internal readonly struct PluginsReader : ISpanReader
+{
+ public static Plugin[] ReadFrom(ref SpanReader reader, ProtocolVersion? p = default, int? i = default, int? outerCount = default)
+ {
+ // ReSharper disable once UnusedVariable
+ var dataSize = reader.Read();
+
+ var count = reader.Read();
+ var plugins = new Plugin[count];
+ for (var j = 0; j < count; j++)
+ {
+ var name = reader.ReadLengthAndString();
+ var description = reader.ReadLengthAndString();
+ var version = reader.ReadLengthAndString();
+ var index = reader.Read();
+ var sdkVersion = reader.Read();
+
+ plugins[j] = new Plugin(name, description, version, index, sdkVersion);
+ }
+
+ return plugins;
+ }
+}
\ No newline at end of file
diff --git a/src/OpenRGB.NET/Readers/PrimitiveReader.cs b/src/OpenRGB.NET/Readers/PrimitiveReader.cs
new file mode 100644
index 0000000..c2307ab
--- /dev/null
+++ b/src/OpenRGB.NET/Readers/PrimitiveReader.cs
@@ -0,0 +1,9 @@
+using OpenRGB.NET.Utils;
+
+namespace OpenRGB.NET;
+
+internal readonly struct PrimitiveReader : ISpanReader where T : unmanaged
+{
+ public static T ReadFrom(ref SpanReader reader, ProtocolVersion? protocolVersion = default, int? index = default, int? outerCount = default) =>
+ reader.Read();
+}
\ No newline at end of file
diff --git a/src/OpenRGB.NET/Readers/ProfilesReader.cs b/src/OpenRGB.NET/Readers/ProfilesReader.cs
new file mode 100644
index 0000000..bfdeb6d
--- /dev/null
+++ b/src/OpenRGB.NET/Readers/ProfilesReader.cs
@@ -0,0 +1,21 @@
+using OpenRGB.NET.Utils;
+
+namespace OpenRGB.NET;
+
+internal readonly struct ProfilesReader : ISpanReader
+{
+ public static string[] ReadFrom(ref SpanReader reader, ProtocolVersion? p = default, int? i = default, int? outerCount = default)
+ {
+ // ReSharper disable once UnusedVariable
+ var dataSize = reader.Read();
+
+ var count = reader.Read();
+ var profiles = new string[count];
+ for (var j = 0; j < count; j++)
+ {
+ profiles[j] = reader.ReadLengthAndString();
+ }
+
+ return profiles;
+ }
+}
\ No newline at end of file
diff --git a/src/OpenRGB.NET/Readers/ProtocolVersionReader.cs b/src/OpenRGB.NET/Readers/ProtocolVersionReader.cs
new file mode 100644
index 0000000..740f099
--- /dev/null
+++ b/src/OpenRGB.NET/Readers/ProtocolVersionReader.cs
@@ -0,0 +1,9 @@
+using OpenRGB.NET.Utils;
+
+namespace OpenRGB.NET;
+
+internal readonly struct ProtocolVersionReader : ISpanReader
+{
+ public static ProtocolVersion ReadFrom(ref SpanReader reader, ProtocolVersion? p = default, int? i = default, int? outerCount = default)
+ => ProtocolVersion.FromNumber(reader.Read());
+}
\ No newline at end of file
diff --git a/src/OpenRGB.NET/Readers/SegmentsReader.cs b/src/OpenRGB.NET/Readers/SegmentsReader.cs
new file mode 100644
index 0000000..2e9d625
--- /dev/null
+++ b/src/OpenRGB.NET/Readers/SegmentsReader.cs
@@ -0,0 +1,25 @@
+using OpenRGB.NET.Utils;
+
+namespace OpenRGB.NET;
+
+internal readonly struct SegmentsReader : ISpanReader
+{
+ public static Segment[] ReadFrom(ref SpanReader reader, ProtocolVersion? protocolVersion = default, int? index = default,
+ int? outerCount = default)
+ {
+ var segmentCount = reader.Read();
+ var segments = new Segment[segmentCount];
+
+ for (var i = 0; i < segmentCount; i++)
+ {
+ var name = reader.ReadLengthAndString();
+ var type = (ZoneType)reader.Read();
+ var start = reader.Read();
+ var ledCount = reader.Read();
+
+ segments[i] = new Segment(i, name, type, start, ledCount);
+ }
+
+ return segments;
+ }
+}
\ No newline at end of file
diff --git a/src/OpenRGB.NET/Readers/ZonesReader.cs b/src/OpenRGB.NET/Readers/ZonesReader.cs
new file mode 100644
index 0000000..d390b48
--- /dev/null
+++ b/src/OpenRGB.NET/Readers/ZonesReader.cs
@@ -0,0 +1,34 @@
+using System;
+using OpenRGB.NET.Utils;
+
+namespace OpenRGB.NET;
+
+internal readonly struct ZonesReader : ISpanReader
+{
+ public static Zone[] ReadFrom(ref SpanReader reader, ProtocolVersion? protocolVersion = default, int? index = default, int? outerCount = default)
+ {
+ if (protocolVersion is not { } protocol)
+ throw new ArgumentNullException(nameof(protocolVersion));
+ if (index is not { } deviceIndex)
+ throw new ArgumentNullException(nameof(index));
+
+ var zoneCount = reader.Read();
+ var zones = new Zone[zoneCount];
+
+ for (var i = 0; i < zoneCount; i++)
+ {
+ var name = reader.ReadLengthAndString();
+ var type = (ZoneType)reader.Read();
+ var ledsMin = reader.Read();
+ var ledsMax = reader.Read();
+ var ledCount = reader.Read();
+ var zoneMatrixLength = reader.Read();
+ var matrixMap = zoneMatrixLength > 0 ? MatrixMapReader.ReadFrom(ref reader) : null;
+ var segments = protocol.SupportsSegmentsAndPlugins ? SegmentsReader.ReadFrom(ref reader, protocolVersion) : [];
+
+ zones[i] = new Zone(i, deviceIndex, name, type, ledCount, ledsMin, ledsMax, matrixMap, segments);
+ }
+
+ return zones;
+ }
+}
\ No newline at end of file
diff --git a/src/OpenRGB.NET/Utils/PacketFactory.cs b/src/OpenRGB.NET/Utils/PacketFactory.cs
deleted file mode 100644
index 80b9954..0000000
--- a/src/OpenRGB.NET/Utils/PacketFactory.cs
+++ /dev/null
@@ -1,195 +0,0 @@
-using System;
-using System.Diagnostics;
-using System.Text;
-
-namespace OpenRGB.NET.Utils;
-
-///
-/// Static class to generate packets for various commands
-///
-internal static class PacketFactory
-{
- //4 uint zone_index
- //4 uint new_zone_size
- internal const int ResizeZoneLength = 4 + 4;
- internal static void WriteResizeZone(Span packet, uint deviceId, uint zoneIndex, uint newZoneSize)
- {
- const int LENGTH = PacketHeader.Length + ResizeZoneLength;
-
- if (packet.Length != LENGTH)
- throw new ArgumentException($"Packet length is {packet.Length} but should be {LENGTH}");
-
- var writer = new SpanWriter(packet);
- var header = new PacketHeader(deviceId, CommandId.ResizeZone, ResizeZoneLength);
-
- header.WriteTo(ref writer);
- writer.WriteUInt32(zoneIndex);
- writer.WriteUInt32(newZoneSize);
- }
-
-
- internal static int GetUpdateLedsLength(int ledCount)
- {
- //4 uint data_size
- //2 ushort led_count
- //4 * led_count uint led_data
-
- return 4 + 2 + 4 * ledCount;
- }
- internal static void WriteUpdateLeds(Span packet, int deviceId, ReadOnlySpan colors)
- {
- var dataLength = GetUpdateLedsLength(colors.Length);
- var length = PacketHeader.Length + dataLength;
-
- if (packet.Length != length)
- throw new ArgumentException($"Packet length is {packet.Length} but should be {length}");
-
- var writer = new SpanWriter(packet);
- var header = new PacketHeader((uint)deviceId, CommandId.UpdateLeds, (uint)dataLength);
-
- header.WriteTo(ref writer);
- writer.WriteUInt32((uint)length);
- writer.WriteUInt16((ushort)colors.Length);
-
- for (var i = 0; i < colors.Length; i++)
- colors[i].WriteTo(ref writer);
- }
-
-
- internal static int GetUpdateZoneLedsLength(int ledCount)
- {
- //4 uint data_size
- //4 uint zone_index
- //2 ushort led_count
- //4 * led_count uint led_data
-
- return 4 + 4 + 2 + 4 * ledCount;
- }
- internal static void WriteUpdateZoneLeds(Span packet, uint deviceId, uint zoneIndex, ReadOnlySpan colors)
- {
- var dataLength = GetUpdateZoneLedsLength(colors.Length);
- var length = PacketHeader.Length + dataLength;
-
- if (packet.Length != length)
- throw new ArgumentException($"Packet length is {packet.Length} but should be {length}");
-
- var writer = new SpanWriter(packet);
- var header = new PacketHeader(deviceId, CommandId.UpdateZoneLeds, (uint)dataLength);
-
- header.WriteTo(ref writer);
- writer.WriteUInt32((uint)length);
- writer.WriteUInt32(zoneIndex);
- writer.WriteUInt16((ushort)colors.Length);
-
- for (var i = 0; i < colors.Length; i++)
- colors[i].WriteTo(ref writer);
- }
-
-
- //4 uint led_index
- //4 color color
- internal const int UpdateSingleLedLength = 4 + 4;
- internal static void WriteUpdateSingleLed(Span packet, uint deviceId, uint ledIndex, Color color)
- {
- const int LENGTH = PacketHeader.Length + UpdateSingleLedLength;
- if (packet.Length != LENGTH)
- throw new ArgumentException($"Packet length is {packet.Length} but should be {LENGTH}");
-
- var writer = new SpanWriter(packet);
- var header = new PacketHeader(deviceId, CommandId.UpdateSingleLed, UpdateSingleLedLength);
-
- header.WriteTo(ref writer);
- writer.WriteUInt32(ledIndex);
- color.WriteTo(ref writer);
- }
-
-
- internal static int GetStringOperationLength(string profile)
- {
- return Encoding.ASCII.GetByteCount(profile) + 1;
- }
- internal static void WriteStringOperation(Span packet, string someString, CommandId operation)
- {
- var dataLength = GetStringOperationLength(someString);
- var length = PacketHeader.Length + dataLength;
-
- if (packet.Length != length)
- throw new ArgumentException($"Packet length is {packet.Length} but should be {length}");
-
- //allocation is fine here, this is not a performance critical path.
- //TODO: when porting to .NET 8 use Encoding.ASCII.TryGetBytes to avoid allocation
- ReadOnlySpan profileName = Encoding.ASCII.GetBytes(someString + '\0');
-
- var header = new PacketHeader(0, operation, (uint)profileName.Length);
- var writer = new SpanWriter(packet);
-
- header.WriteTo(ref writer);
- profileName.CopyTo(packet[PacketHeader.Length..]);
- //Encoding.ASCII.TryGetBytes(profile + '\0', packet[PacketHeader.Length..], out var bytesWritten);
- }
-
- internal static int GetModeOperationLength(Mode mode)
- {
- //4 uint length
- //4 uint mode_index
- //x mode data
-
- return 4 + 4 + mode.GetLength();
- }
- internal static void WriteModeOperation(Span packet, uint deviceId, uint modeIndex, Mode mode, CommandId modeOperation)
- {
- var dataLength = GetModeOperationLength(mode);
- var length = PacketHeader.Length + dataLength;
-
- if (packet.Length != length)
- throw new ArgumentException($"Packet length is {packet.Length} but should be {length}");
-
- var writer = new SpanWriter(packet);
- var header = new PacketHeader(deviceId, modeOperation, (uint)dataLength);
-
- header.WriteTo(ref writer);
- writer.WriteUInt32((uint)dataLength);
- writer.WriteUInt32(modeIndex);
- mode.WriteTo(ref writer);
- }
-
-
- internal const int ProtocolVersionLength = 4;
- internal static void WriteProtocolVersion(Span packet,uint deviceId, uint protocolVersion, CommandId commandId)
- {
- const int LENGTH = PacketHeader.Length + ProtocolVersionLength;
-
- if (packet.Length != LENGTH)
- throw new ArgumentException($"Packet length is {packet.Length} but should be {LENGTH}");
-
- var writer = new SpanWriter(packet);
- var header = new PacketHeader(deviceId, commandId, ProtocolVersionLength);
-
- header.WriteTo(ref writer);
- writer.WriteUInt32(protocolVersion);
- }
-
- internal static int GetPluginSpecificLength(ReadOnlySpan data)
- {
- //4 uint plugin_packet_type
- //x plugin_data
-
- return 4 + data.Length;
- }
- public static void WritePluginSpecific(Span packet, uint pluginId, uint pluginPacketType, ReadOnlySpan data)
- {
- var dataLength = GetPluginSpecificLength(data);
- var length = PacketHeader.Length + dataLength;
-
- if (packet.Length != length)
- throw new ArgumentException($"Packet length is {packet.Length} but should be {length}");
-
- var writer = new SpanWriter(packet);
- var header = new PacketHeader(pluginId, CommandId.PluginSpecific, (uint)data.Length);
-
- header.WriteTo(ref writer);
- writer.WriteUInt32(pluginPacketType);
- if (data.Length > 0)
- writer.WriteBytes(data);
- }
-}
\ No newline at end of file
diff --git a/src/OpenRGB.NET/Utils/SocketExtensions.cs b/src/OpenRGB.NET/Utils/SocketExtensions.cs
index 6160dd3..7c62cd5 100644
--- a/src/OpenRGB.NET/Utils/SocketExtensions.cs
+++ b/src/OpenRGB.NET/Utils/SocketExtensions.cs
@@ -9,13 +9,46 @@ internal static class SocketExtensions
{
public static void Connect(this Socket socket, string host, int port, int timeoutMs, CancellationToken cancellationToken)
{
- var result = socket.ConnectAsync(host, port);
+ var result = socket.ConnectAsync(host, port);
Task.WaitAny([result], timeoutMs, cancellationToken);
-
+
if (socket.Connected)
return;
-
+
socket.Close();
throw new TimeoutException("Could not connect to OpenRGB");
}
+
+ public static async Task ReceiveAllAsync(this Socket socket, Memory buffer, CancellationToken cancellationToken)
+ {
+ var recv = 0;
+ while (recv < buffer.Length)
+ {
+ var received = await socket.ReceiveAsync(buffer[recv..], SocketFlags.None, cancellationToken);
+ if (received == 0)
+ break;
+
+ recv += received;
+ }
+ }
+
+ public static async Task SendAllAsync(this Socket socket, ReadOnlyMemory buffer, CancellationToken cancellationToken)
+ {
+ var sent = 0;
+ while (sent < buffer.Length)
+ {
+ var sentBytes = await socket.SendAsync(buffer[sent..], SocketFlags.None, cancellationToken);
+ sent += sentBytes;
+ }
+ }
+
+ public static void SendAll(this Socket socket, ReadOnlySpan buffer)
+ {
+ var sent = 0;
+ while (sent < buffer.Length)
+ {
+ var sentBytes = socket.Send(buffer[sent..]);
+ sent += sentBytes;
+ }
+ }
}
\ No newline at end of file
diff --git a/src/OpenRGB.NET/Utils/SpanReader.cs b/src/OpenRGB.NET/Utils/SpanReader.cs
index e6f2a6d..1b53a8f 100644
--- a/src/OpenRGB.NET/Utils/SpanReader.cs
+++ b/src/OpenRGB.NET/Utils/SpanReader.cs
@@ -1,43 +1,24 @@
using System;
-using System.Buffers.Binary;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
using System.Text;
namespace OpenRGB.NET.Utils;
#if DEBUG
[NonCopyable]
#endif
-internal ref struct SpanReader
+internal ref struct SpanReader(ReadOnlySpan span)
{
- public ReadOnlySpan Span { get; }
- public int Position { get; private set; }
+ private ReadOnlySpan Span { get; } = span;
+ private int Position { get; set; } = 0;
- public SpanReader(ReadOnlySpan span)
+ internal T Read() where T : unmanaged
{
- Span = span;
- Position = 0;
- }
-
- public ushort ReadUInt16()
- {
- var value = BinaryPrimitives.ReadUInt16LittleEndian(Span[Position..]);
- Position += sizeof(ushort);
- return value;
- }
-
- public int ReadInt32()
- {
- var value = BinaryPrimitives.ReadInt32LittleEndian(Span[Position..]);
- Position += sizeof(int);
- return value;
- }
-
- public uint ReadUInt32()
- {
- var value = BinaryPrimitives.ReadUInt32LittleEndian(Span[Position..]);
- Position += sizeof(uint);
+ var value = MemoryMarshal.Read(Span[Position..]);
+ Position += Unsafe.SizeOf();
return value;
}
-
+
public ReadOnlySpan ReadBytes(int length)
{
var value = Span[Position..(Position + length)];
@@ -45,16 +26,13 @@ public ReadOnlySpan ReadBytes(int length)
return value;
}
- public byte ReadByte()
- {
- var value = Span[Position];
- Position += sizeof(byte);
- return value;
- }
-
public string ReadLengthAndString()
{
- int length = ReadUInt16();
+ int length = Read();
+
+ if (length == 0)
+ return string.Empty;
+
return Encoding.ASCII.GetString(ReadBytes(length)[..^1]);
}
}
\ No newline at end of file
diff --git a/src/OpenRGB.NET/Utils/SpanWriter.cs b/src/OpenRGB.NET/Utils/SpanWriter.cs
index 76b79e0..3339323 100644
--- a/src/OpenRGB.NET/Utils/SpanWriter.cs
+++ b/src/OpenRGB.NET/Utils/SpanWriter.cs
@@ -1,5 +1,6 @@
using System;
-using System.Buffers.Binary;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
using System.Text;
namespace OpenRGB.NET.Utils;
@@ -11,41 +12,30 @@ internal ref struct SpanWriter(Span span)
{
public Span Span { get; } = span;
public int Position { get; private set; } = 0;
-
- public void WriteUInt16(ushort value)
- {
- BinaryPrimitives.WriteUInt16LittleEndian(Span[Position..], value);
- Position += sizeof(ushort);
- }
-
- public void WriteInt32(int value)
- {
- BinaryPrimitives.WriteInt32LittleEndian(Span[Position..], value);
- Position += sizeof(int);
- }
-
- public void WriteUInt32(uint value)
+
+ public void Write(T value) where T : unmanaged
{
- BinaryPrimitives.WriteUInt32LittleEndian(Span[Position..], value);
- Position += sizeof(uint);
+ MemoryMarshal.Write(Span[Position..], in value);
+ Position += Unsafe.SizeOf();
}
- public void WriteBytes(ReadOnlySpan span)
+ public void Write(ReadOnlySpan span)
{
span.CopyTo(Span[Position..]);
Position += span.Length;
}
- public void WriteByte(byte value)
+ public void WriteLengthAndString(string value)
{
- Span[Position] = value;
- Position += sizeof(byte);
+ Write((ushort)(value.Length + 1));
+ Write(value);
}
-
- public void WriteLengthAndString(string value)
+
+ public void Write(string value)
{
- WriteUInt16((ushort)(value.Length + 1));
- WriteBytes(Encoding.ASCII.GetBytes(value));
- WriteByte(0);
+ var byteCount = Encoding.ASCII.GetByteCount(value.AsSpan());
+ Encoding.ASCII.GetBytes(value, Span.Slice(Position, byteCount));
+ Position += byteCount;
+ Write(0);
}
}
\ No newline at end of file
diff --git a/src/OpenRGB.NET/Writables/Args.cs b/src/OpenRGB.NET/Writables/Args.cs
new file mode 100644
index 0000000..bbcdd52
--- /dev/null
+++ b/src/OpenRGB.NET/Writables/Args.cs
@@ -0,0 +1,39 @@
+using System.Runtime.CompilerServices;
+using OpenRGB.NET.Utils;
+
+namespace OpenRGB.NET;
+
+internal readonly record struct Args(T1 Arg1) : ISpanWritable where T1 : unmanaged
+{
+ public int Length => Unsafe.SizeOf();
+ public void WriteTo(ref SpanWriter writer)
+ {
+ writer.Write(Arg1);
+ }
+}
+
+internal readonly record struct Args(T1 Arg1, T2 Arg2) : ISpanWritable
+ where T1 : unmanaged
+ where T2 : unmanaged
+{
+ public int Length => Unsafe.SizeOf() + Unsafe.SizeOf();
+ public void WriteTo(ref SpanWriter writer)
+ {
+ writer.Write(Arg1);
+ writer.Write(Arg2);
+ }
+}
+
+internal readonly record struct Args(T1 Arg1, T2 Arg2, T3 Arg3) : ISpanWritable
+ where T1 : unmanaged
+ where T2 : unmanaged
+ where T3 : unmanaged
+{
+ public int Length => Unsafe.SizeOf() + Unsafe.SizeOf() + Unsafe.SizeOf();
+ public void WriteTo(ref SpanWriter writer)
+ {
+ writer.Write(Arg1);
+ writer.Write(Arg2);
+ writer.Write(Arg3);
+ }
+}
\ No newline at end of file
diff --git a/src/OpenRGB.NET/Writables/EmptyArg.cs b/src/OpenRGB.NET/Writables/EmptyArg.cs
new file mode 100644
index 0000000..542f61a
--- /dev/null
+++ b/src/OpenRGB.NET/Writables/EmptyArg.cs
@@ -0,0 +1,9 @@
+using OpenRGB.NET.Utils;
+
+namespace OpenRGB.NET;
+
+internal readonly struct EmptyArg : ISpanWritable
+{
+ public int Length => 0;
+ public void WriteTo(ref SpanWriter writer) { }
+}
\ No newline at end of file
diff --git a/src/OpenRGB.NET/Writables/ModeArg.cs b/src/OpenRGB.NET/Writables/ModeArg.cs
new file mode 100644
index 0000000..18a2e9d
--- /dev/null
+++ b/src/OpenRGB.NET/Writables/ModeArg.cs
@@ -0,0 +1,53 @@
+using System.Runtime.InteropServices;
+using OpenRGB.NET.Utils;
+
+namespace OpenRGB.NET;
+
+internal readonly record struct ModeArg(Mode Mode) : ISpanWritable
+{
+ public int Length
+ {
+ get
+ {
+ var size = 0;
+
+ size += sizeof(ushort) * 2;// Name Length, Colors Length
+ size += sizeof(uint) * 9;// Value, SpeedMin, SpeedMax, ColorMin, ColorMax, Speed, Direction, ColorMode, Flags
+ size += Mode.Name.Length + 1;// Name
+ size += sizeof(uint) * Mode.Colors.Length;// Colors
+ if (Mode.ProtocolVersion.SupportsBrightnessAndSaveMode)
+ size += sizeof(uint) * 3;// BrightnessMin, BrightnessMax, Brightness
+
+ return size;
+ }
+ }
+
+ public void WriteTo(ref SpanWriter writer)
+ {
+ writer.WriteLengthAndString(Mode.Name);
+ writer.Write(Mode.Value);
+ writer.Write((uint)Mode.Flags);
+ writer.Write(Mode.SpeedMin);
+ writer.Write(Mode.SpeedMax);
+
+ if (Mode.ProtocolVersion.SupportsBrightnessAndSaveMode)
+ {
+ writer.Write(Mode.BrightnessMin);
+ writer.Write(Mode.BrightnessMax);
+ }
+
+ writer.Write(Mode.ColorMin);
+ writer.Write(Mode.ColorMax);
+ writer.Write(Mode.Speed);
+
+ if (Mode.ProtocolVersion.SupportsBrightnessAndSaveMode)
+ writer.Write(Mode.Brightness);
+
+ writer.Write((uint)Mode.Direction);
+ writer.Write((uint)Mode.ColorMode);
+ writer.Write((ushort)Mode.Colors.Length);
+
+ var colors = MemoryMarshal.Cast(Mode.Colors);
+ writer.Write(colors);
+ }
+}
\ No newline at end of file
diff --git a/src/OpenRGB.NET/Writables/ModeOperationArg.cs b/src/OpenRGB.NET/Writables/ModeOperationArg.cs
new file mode 100644
index 0000000..2dd59e8
--- /dev/null
+++ b/src/OpenRGB.NET/Writables/ModeOperationArg.cs
@@ -0,0 +1,14 @@
+using OpenRGB.NET.Utils;
+
+namespace OpenRGB.NET;
+
+internal readonly record struct ModeOperationArg(uint DataSize, uint ModeId, ModeArg Mode) : ISpanWritable
+{
+ public int Length => sizeof(uint) * 2 + Mode.Length;
+ public void WriteTo(ref SpanWriter writer)
+ {
+ writer.Write(DataSize);
+ writer.Write(ModeId);
+ Mode.WriteTo(ref writer);
+ }
+}
diff --git a/src/OpenRGB.NET/Writables/PacketHeader.cs b/src/OpenRGB.NET/Writables/PacketHeader.cs
new file mode 100644
index 0000000..2211a65
--- /dev/null
+++ b/src/OpenRGB.NET/Writables/PacketHeader.cs
@@ -0,0 +1,41 @@
+using System;
+using OpenRGB.NET.Utils;
+
+namespace OpenRGB.NET;
+
+///
+/// Packet Header class containing the command ID and the length of the data to be sent.
+///
+internal readonly struct PacketHeader(uint deviceId, CommandId command, uint length) : ISpanWritable
+{
+ internal const int LENGTH = 16;
+ internal static ReadOnlySpan MagicBytes => "ORGB"u8;
+
+ internal uint DeviceId { get; } = deviceId;
+ internal CommandId Command { get; } = command;
+ internal uint DataLength { get; } = length;
+
+ public int Length => LENGTH;
+
+ public static PacketHeader FromSpan(ReadOnlySpan span)
+ {
+ var reader = new SpanReader(span);
+ var magicBytes = reader.ReadBytes(4);
+ if (!magicBytes.SequenceEqual(MagicBytes))
+ throw new ArgumentException($"Magic bytes \"ORGB\" were not found");
+
+ var device = reader.Read();
+ var command = (CommandId)reader.Read();
+ var length = reader.Read();
+
+ return new PacketHeader(device, command, length);
+ }
+
+ public void WriteTo(ref SpanWriter writer)
+ {
+ writer.Write(MagicBytes);
+ writer.Write(DeviceId);
+ writer.Write((uint)Command);
+ writer.Write(DataLength);
+ }
+}
\ No newline at end of file
diff --git a/src/OpenRGB.NET/Writables/ProtocolVersionArg.cs b/src/OpenRGB.NET/Writables/ProtocolVersionArg.cs
new file mode 100644
index 0000000..fd444be
--- /dev/null
+++ b/src/OpenRGB.NET/Writables/ProtocolVersionArg.cs
@@ -0,0 +1,9 @@
+using OpenRGB.NET.Utils;
+
+namespace OpenRGB.NET;
+
+internal readonly record struct ProtocolVersionArg(ProtocolVersion Version) : ISpanWritable
+{
+ public int Length => sizeof(uint);
+ public void WriteTo(ref SpanWriter writer) => writer.Write(Version.Number);
+}
\ No newline at end of file
diff --git a/src/OpenRGB.NET/Writables/StringArg.cs b/src/OpenRGB.NET/Writables/StringArg.cs
new file mode 100644
index 0000000..4bae7e8
--- /dev/null
+++ b/src/OpenRGB.NET/Writables/StringArg.cs
@@ -0,0 +1,9 @@
+using OpenRGB.NET.Utils;
+
+namespace OpenRGB.NET;
+
+internal readonly record struct StringArg(string Value) : ISpanWritable
+{
+ public int Length => Value.Length + 1;
+ public void WriteTo(ref SpanWriter writer) => writer.Write(Value);
+}
\ No newline at end of file