aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs77
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs280
2 files changed, 314 insertions, 43 deletions
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
index 20a4d87fb..a7fda1d72 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
@@ -130,9 +130,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
int receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
- ParseReturnMessage(buffer, receivedBytes, out string returnVal);
-
- return string.Equals(returnVal, "none", StringComparison.OrdinalIgnoreCase);
+ return VerifyReturnValueOfGetSet(buffer.AsSpan(receivedBytes), "none");
}
finally
{
@@ -173,7 +171,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
int receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
// parse response to make sure it worked
- if (!ParseReturnMessage(buffer, receivedBytes, out _))
+ if (!TryGetReturnValueOfGetSet(buffer.AsSpan(0, receivedBytes), out _))
{
continue;
}
@@ -185,7 +183,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
// parse response to make sure it worked
- if (!ParseReturnMessage(buffer, receivedBytes, out _))
+ if (!TryGetReturnValueOfGetSet(buffer.AsSpan(0, receivedBytes), out _))
{
await ReleaseLockkey(_tcpClient, lockKeyValue).ConfigureAwait(false);
continue;
@@ -199,7 +197,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
// parse response to make sure it worked
- if (!ParseReturnMessage(buffer, receivedBytes, out _))
+ if (!TryGetReturnValueOfGetSet(buffer.AsSpan(0, receivedBytes), out _))
{
await ReleaseLockkey(_tcpClient, lockKeyValue).ConfigureAwait(false);
continue;
@@ -239,7 +237,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
int receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
// parse response to make sure it worked
- if (!ParseReturnMessage(buffer, receivedBytes, out _))
+ if (!TryGetReturnValueOfGetSet(buffer.AsSpan(0, receivedBytes), out _))
{
return;
}
@@ -292,7 +290,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return FinishPacket(buffer, offset);
}
- private static int WriteSetMessage(Span<byte> buffer, int tuner, string name, string value, uint? lockkey)
+ internal static int WriteSetMessage(Span<byte> buffer, int tuner, string name, string value, uint? lockkey)
{
var byteName = string.Format(CultureInfo.InvariantCulture, "/tuner{0}/{1}", tuner, name);
int offset = WriteHeaderAndPayload(buffer, byteName);
@@ -355,60 +353,65 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return offset + 4;
}
- private static bool ParseReturnMessage(byte[] buf, int numBytes, out string returnVal)
+ internal static bool VerifyReturnValueOfGetSet(ReadOnlySpan<byte> buffer, string expected)
{
- returnVal = string.Empty;
+ return TryGetReturnValueOfGetSet(buffer, out var value)
+ && string.Equals(Encoding.UTF8.GetString(value), expected, StringComparison.OrdinalIgnoreCase);
+ }
- if (numBytes < 4)
+ internal static bool TryGetReturnValueOfGetSet(ReadOnlySpan<byte> buffer, out ReadOnlySpan<byte> value)
+ {
+ value = ReadOnlySpan<byte>.Empty;
+
+ if (buffer.Length < 8)
{
return false;
}
- var flipEndian = BitConverter.IsLittleEndian;
- int offset = 0;
- byte[] msgTypeBytes = new byte[2];
- Buffer.BlockCopy(buf, offset, msgTypeBytes, 0, msgTypeBytes.Length);
-
- if (flipEndian)
+ uint crc = BinaryPrimitives.ReadUInt32LittleEndian(buffer[^4..]);
+ if (crc != Crc32.Compute(buffer[..^4]))
{
- Array.Reverse(msgTypeBytes);
+ return false;
}
- var msgType = BitConverter.ToUInt16(msgTypeBytes, 0);
- offset += 2;
-
- if (msgType != GetSetReply)
+ if (BinaryPrimitives.ReadUInt16BigEndian(buffer) != GetSetReply)
{
return false;
}
- byte[] msgLengthBytes = new byte[2];
- Buffer.BlockCopy(buf, offset, msgLengthBytes, 0, msgLengthBytes.Length);
- if (flipEndian)
+ var msgLength = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(2));
+ if (buffer.Length != 2 + 2 + 4 + msgLength)
{
- Array.Reverse(msgLengthBytes);
+ return false;
}
- var msgLength = BitConverter.ToUInt16(msgLengthBytes, 0);
- offset += 2;
-
- if (numBytes < msgLength + 8)
+ var offset = 4;
+ if (buffer[offset++] != GetSetName)
{
return false;
}
- offset++; // Name Tag
-
- var nameLength = buf[offset++];
+ var nameLength = buffer[offset++];
+ if (buffer.Length < 4 + 1 + offset + nameLength)
+ {
+ return false;
+ }
- // skip the name field to get to value for return
offset += nameLength;
- offset++; // Value Tag
+ if (buffer[offset++] != GetSetValue)
+ {
+ return false;
+ }
- var valueLength = buf[offset++];
+ var valueLength = buffer[offset++];
+ if (buffer.Length < 4 + offset + valueLength)
+ {
+ return false;
+ }
- returnVal = Encoding.UTF8.GetString(buf, offset, valueLength - 1); // remove null terminator
+ // remove null terminator
+ value = buffer.Slice(offset, valueLength - 1);
return true;
}
}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs
index 7e04a1ec1..fd499d9cf 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs
@@ -1,4 +1,5 @@
using System;
+using System.Text;
using Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun;
using Xunit;
@@ -17,8 +18,9 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv
Span<byte> buffer = stackalloc byte[128];
int len = HdHomerunManager.WriteNullTerminatedString(buffer, string.Empty);
- Assert.Equal(expected.Length, len);
- Assert.True(expected.SequenceEqual(buffer.Slice(0, len)));
+ Assert.Equal(
+ Convert.ToHexString(expected),
+ Convert.ToHexString(buffer.Slice(0, len)));
}
[Fact]
@@ -32,8 +34,9 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv
Span<byte> buffer = stackalloc byte[128];
int len = HdHomerunManager.WriteNullTerminatedString(buffer, "The quick");
- Assert.Equal(expected.Length, len);
- Assert.True(expected.SequenceEqual(buffer.Slice(0, len)));
+ Assert.Equal(
+ Convert.ToHexString(expected),
+ Convert.ToHexString(buffer.Slice(0, len)));
}
[Fact]
@@ -51,8 +54,273 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv
Span<byte> buffer = stackalloc byte[128];
int len = HdHomerunManager.WriteGetMessage(buffer, 0, "N");
- Assert.Equal(expected.Length, len);
- Assert.True(expected.SequenceEqual(buffer.Slice(0, len)));
+ Assert.Equal(
+ Convert.ToHexString(expected),
+ Convert.ToHexString(buffer.Slice(0, len)));
+ }
+
+ [Fact]
+ public void WriteSetMessage_NoLockKey_Success()
+ {
+ ReadOnlySpan<byte> expected = stackalloc byte[]
+ {
+ 0, 4,
+ 0, 20,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0xa9, 0x49, 0xd0, 0x68
+ };
+
+ Span<byte> buffer = stackalloc byte[128];
+ int len = HdHomerunManager.WriteSetMessage(buffer, 0, "N", "value", null);
+
+ Assert.Equal(
+ Convert.ToHexString(expected),
+ Convert.ToHexString(buffer.Slice(0, len)));
+ }
+
+ [Fact]
+ public void WriteSetMessage_LockKey_Success()
+ {
+ ReadOnlySpan<byte> expected = stackalloc byte[]
+ {
+ 0, 4,
+ 0, 26,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 21,
+ 4, 0x00, 0x01, 0x38, 0xd5,
+ 0x8e, 0xb6, 0x06, 0x82
+ };
+
+ Span<byte> buffer = stackalloc byte[128];
+ int len = HdHomerunManager.WriteSetMessage(buffer, 0, "N", "value", 80085);
+
+ Assert.Equal(
+ Convert.ToHexString(expected),
+ Convert.ToHexString(buffer.Slice(0, len)));
+ }
+
+ [Fact]
+ public void TryGetReturnValueOfGetSet_Valid_Success()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 5,
+ 0, 20,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0x7d, 0xa3, 0xa3, 0xf3
+ };
+
+ Assert.True(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out var value));
+ Assert.Equal("value", Encoding.UTF8.GetString(value));
+ }
+
+ [Fact]
+ public void TryGetReturnValueOfGetSet_InvalidCrc_False()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 5,
+ 0, 20,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0x7d, 0xa3, 0xa3, 0xf4
+ };
+
+ Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _));
+ }
+
+ [Fact]
+ public void TryGetReturnValueOfGetSet_InvalidPacketType_False()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 4,
+ 0, 20,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0xa9, 0x49, 0xd0, 0x68
+ };
+
+ Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _));
+ }
+
+ [Fact]
+ public void TryGetReturnValueOfGetSet_InvalidPacket_False()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 5,
+ 0, 20,
+ 0x7d, 0xa3, 0xa3
+ };
+
+ Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _));
+ }
+
+ [Fact]
+ public void TryGetReturnValueOfGetSet_TooSmallMessageLength_False()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 5,
+ 0, 19,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0x25, 0x25, 0x44, 0x9a
+ };
+
+ Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _));
+ }
+
+ [Fact]
+ public void TryGetReturnValueOfGetSet_TooLargeMessageLength_False()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 5,
+ 0, 21,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0xe3, 0x20, 0x79, 0x6c
+ };
+
+ Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _));
+ }
+
+ [Fact]
+ public void TryGetReturnValueOfGetSet_TooLargeNameLength_False()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 5,
+ 0, 20,
+ 3,
+ 20, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0xe1, 0x8e, 0x9c, 0x74
+ };
+
+ Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _));
+ }
+
+ [Fact]
+ public void TryGetReturnValueOfGetSet_InvalidGetSetNameTag_False()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 5,
+ 0, 20,
+ 4,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0xee, 0x05, 0xe7, 0x12
+ };
+
+ Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _));
+ }
+
+ [Fact]
+ public void TryGetReturnValueOfGetSet_InvalidGetSetValueTag_False()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 5,
+ 0, 20,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 3,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0x64, 0xaa, 0x66, 0xf9
+ };
+
+ Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _));
+ }
+
+ [Fact]
+ public void TryGetReturnValueOfGetSet_TooLargeValueLength_False()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 5,
+ 0, 20,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 7, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0xc9, 0xa8, 0xd4, 0x55
+ };
+
+ Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _));
+ }
+
+ [Fact]
+ public void VerifyReturnValueOfGetSet_Valid_True()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 5,
+ 0, 20,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0x7d, 0xa3, 0xa3, 0xf3
+ };
+
+ Assert.True(HdHomerunManager.VerifyReturnValueOfGetSet(packet, "value"));
+ }
+
+ [Fact]
+ public void VerifyReturnValueOfGetSet_WrongValue_False()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 5,
+ 0, 20,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0x7d, 0xa3, 0xa3, 0xf3
+ };
+
+ Assert.False(HdHomerunManager.VerifyReturnValueOfGetSet(packet, "none"));
+ }
+
+ [Fact]
+ public void VerifyReturnValueOfGetSet_InvalidPacket_False()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 4,
+ 0, 20,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0x7d, 0xa3, 0xa3, 0xf3
+ };
+
+ Assert.False(HdHomerunManager.VerifyReturnValueOfGetSet(packet, "value"));
}
}
}