From df43364116d8909ec154a9e98da019641d55cabc Mon Sep 17 00:00:00 2001 From: SiuTung08 Date: Sat, 9 Nov 2024 19:55:00 +0000 Subject: [PATCH 1/4] open Header.Writer class and create GetBytesLength method --- .../{Internal => }/HeaderWriter.cs | 45 ++++++++++++++----- .../NatsHeaderTest.cs | 19 ++++++++ 2 files changed, 53 insertions(+), 11 deletions(-) rename src/NATS.Client.Core/{Internal => }/HeaderWriter.cs (77%) diff --git a/src/NATS.Client.Core/Internal/HeaderWriter.cs b/src/NATS.Client.Core/HeaderWriter.cs similarity index 77% rename from src/NATS.Client.Core/Internal/HeaderWriter.cs rename to src/NATS.Client.Core/HeaderWriter.cs index 8bb62694d..7cb1b5974 100644 --- a/src/NATS.Client.Core/Internal/HeaderWriter.cs +++ b/src/NATS.Client.Core/HeaderWriter.cs @@ -1,11 +1,11 @@ using System.Buffers; -using System.IO.Pipelines; using System.Text; using NATS.Client.Core.Commands; +using NATS.Client.Core.Internal; -namespace NATS.Client.Core.Internal; +namespace NATS.Client.Core; -internal class HeaderWriter +public class HeaderWriter { private const byte ByteCr = (byte)'\r'; private const byte ByteLf = (byte)'\n'; @@ -21,6 +21,37 @@ internal class HeaderWriter private static ReadOnlySpan ColonSpace => new[] { ByteColon, ByteSpace }; + public long GetBytesLength(NatsHeaders headers) + { + var len = CommandConstants.NatsHeaders10NewLine.Length; + foreach (var kv in headers) + { + foreach (var value in kv.Value) + { + if (value != null) + { + // key length + var keyLength = _encoding.GetByteCount(kv.Key); + len += keyLength; + + // colon space length + len += ColonSpace.Length; + + // value length + var valueLength = _encoding.GetByteCount(value); + len += valueLength; + + // CrLf length + len += CrLf.Length; + } + } + } + + // CrLf length for empty headers + len += CrLf.Length; + return len; + } + internal long Write(IBufferWriter bufferWriter, NatsHeaders headers) { bufferWriter.WriteSpan(CommandConstants.NatsHeaders10NewLine); @@ -53,9 +84,7 @@ internal long Write(IBufferWriter bufferWriter, NatsHeaders headers) var valueSpan = bufferWriter.GetSpan(valueLength); _encoding.GetBytes(value, valueSpan); if (!ValidateValue(valueSpan.Slice(0, valueLength))) - { throw new NatsException($"Invalid header value for key '{kv.Key}': contains CRLF"); - } bufferWriter.Advance(valueLength); len += valueLength; @@ -81,9 +110,7 @@ private static bool ValidateKey(ReadOnlySpan span) foreach (var b in span) { if (b <= ByteSpace || b == ByteColon || b >= ByteDel) - { return false; - } } return true; @@ -96,15 +123,11 @@ private static bool ValidateValue(ReadOnlySpan span) { var pos = span.IndexOf(ByteCr); if (pos == -1 || pos == span.Length - 1) - { return true; - } pos += 1; if (span[pos] == ByteLf) - { return false; - } span = span[pos..]; } diff --git a/tests/NATS.Client.CoreUnit.Tests/NatsHeaderTest.cs b/tests/NATS.Client.CoreUnit.Tests/NatsHeaderTest.cs index 240a0700e..523dd7e18 100644 --- a/tests/NATS.Client.CoreUnit.Tests/NatsHeaderTest.cs +++ b/tests/NATS.Client.CoreUnit.Tests/NatsHeaderTest.cs @@ -195,4 +195,23 @@ public void GetLastValueTests(string text, bool expectedResult, string? expected Assert.Equal(result, expectedResult); Assert.Equal(lastValue, expectedLastValue); } + + [Fact] + public void GetBytesLengthTest() + { + var headers = new NatsHeaders + { + ["k1"] = "v1", + ["k2"] = new[] { "v2-0", "v2-1" }, + ["a-long-header-key"] = "value", + ["key"] = "a-long-header-value", + }; + var writer = new HeaderWriter(Encoding.UTF8); + var bytesLength = writer.GetBytesLength(headers); + + var text = "NATS/1.0\r\nk1: v1\r\nk2: v2-0\r\nk2: v2-1\r\na-long-header-key: value\r\nkey: a-long-header-value\r\n\r\n"; + var expected = new ReadOnlySequence(Encoding.UTF8.GetBytes(text)); + + Assert.Equal(expected.Length, bytesLength); + } } From 227d475337467bc0b74786c0b065a41ed6e892d2 Mon Sep 17 00:00:00 2001 From: SiuTung08 Date: Mon, 11 Nov 2024 19:56:23 +0000 Subject: [PATCH 2/4] create a convenience method on the header class --- src/NATS.Client.Core/NatsHeaders.cs | 10 ++++++++++ tests/NATS.Client.CoreUnit.Tests/NatsHeaderTest.cs | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/NATS.Client.Core/NatsHeaders.cs b/src/NATS.Client.Core/NatsHeaders.cs index f7bec3f67..3dcf429b6 100644 --- a/src/NATS.Client.Core/NatsHeaders.cs +++ b/src/NATS.Client.Core/NatsHeaders.cs @@ -294,6 +294,16 @@ public void CopyTo(KeyValuePair[] array, int arrayIndex) } } + /// + /// Returns the bytes length of the header + /// + /// an instance of headerWriter + /// bytes length of th header + public long GetBytesLength(HeaderWriter headerWriter) + { + return headerWriter.GetBytesLength(this); + } + /// /// Removes the given item from the the collection. /// diff --git a/tests/NATS.Client.CoreUnit.Tests/NatsHeaderTest.cs b/tests/NATS.Client.CoreUnit.Tests/NatsHeaderTest.cs index 523dd7e18..a918a79de 100644 --- a/tests/NATS.Client.CoreUnit.Tests/NatsHeaderTest.cs +++ b/tests/NATS.Client.CoreUnit.Tests/NatsHeaderTest.cs @@ -207,7 +207,7 @@ public void GetBytesLengthTest() ["key"] = "a-long-header-value", }; var writer = new HeaderWriter(Encoding.UTF8); - var bytesLength = writer.GetBytesLength(headers); + var bytesLength = headers.GetBytesLength(writer); var text = "NATS/1.0\r\nk1: v1\r\nk2: v2-0\r\nk2: v2-1\r\na-long-header-key: value\r\nkey: a-long-header-value\r\n\r\n"; var expected = new ReadOnlySequence(Encoding.UTF8.GetBytes(text)); From ffa37d73a8cb52e330742689ca3c4072af21f4f4 Mon Sep 17 00:00:00 2001 From: SiuTung08 Date: Tue, 12 Nov 2024 20:20:06 +0000 Subject: [PATCH 3/4] making HeaderWriter internal again --- .../{ => Internal}/HeaderWriter.cs | 16 ++++++---------- src/NATS.Client.Core/NatsHeaders.cs | 14 ++++++++++---- .../NATS.Client.CoreUnit.Tests/NatsHeaderTest.cs | 4 ++-- 3 files changed, 18 insertions(+), 16 deletions(-) rename src/NATS.Client.Core/{ => Internal}/HeaderWriter.cs (88%) diff --git a/src/NATS.Client.Core/HeaderWriter.cs b/src/NATS.Client.Core/Internal/HeaderWriter.cs similarity index 88% rename from src/NATS.Client.Core/HeaderWriter.cs rename to src/NATS.Client.Core/Internal/HeaderWriter.cs index 7cb1b5974..286fd9998 100644 --- a/src/NATS.Client.Core/HeaderWriter.cs +++ b/src/NATS.Client.Core/Internal/HeaderWriter.cs @@ -1,11 +1,10 @@ using System.Buffers; using System.Text; using NATS.Client.Core.Commands; -using NATS.Client.Core.Internal; -namespace NATS.Client.Core; +namespace NATS.Client.Core.Internal; -public class HeaderWriter +internal class HeaderWriter { private const byte ByteCr = (byte)'\r'; private const byte ByteLf = (byte)'\n'; @@ -21,7 +20,7 @@ public class HeaderWriter private static ReadOnlySpan ColonSpace => new[] { ByteColon, ByteSpace }; - public long GetBytesLength(NatsHeaders headers) + internal static long GetBytesLength(NatsHeaders headers, Encoding encoding) { var len = CommandConstants.NatsHeaders10NewLine.Length; foreach (var kv in headers) @@ -31,14 +30,14 @@ public long GetBytesLength(NatsHeaders headers) if (value != null) { // key length - var keyLength = _encoding.GetByteCount(kv.Key); + var keyLength = encoding.GetByteCount(kv.Key); len += keyLength; // colon space length len += ColonSpace.Length; // value length - var valueLength = _encoding.GetByteCount(value); + var valueLength = encoding.GetByteCount(value); len += valueLength; // CrLf length @@ -68,10 +67,7 @@ internal long Write(IBufferWriter bufferWriter, NatsHeaders headers) var keySpan = bufferWriter.GetSpan(keyLength); _encoding.GetBytes(kv.Key, keySpan); if (!ValidateKey(keySpan.Slice(0, keyLength))) - { - throw new NatsException( - $"Invalid header key '{kv.Key}': contains colon, space, or other non-printable ASCII characters"); - } + throw new NatsException($"Invalid header key '{kv.Key}': contains colon, space, or other non-printable ASCII characters"); bufferWriter.Advance(keyLength); len += keyLength; diff --git a/src/NATS.Client.Core/NatsHeaders.cs b/src/NATS.Client.Core/NatsHeaders.cs index 3dcf429b6..d8d447edb 100644 --- a/src/NATS.Client.Core/NatsHeaders.cs +++ b/src/NATS.Client.Core/NatsHeaders.cs @@ -1,7 +1,9 @@ using System.Collections; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Text; using Microsoft.Extensions.Primitives; +using NATS.Client.Core.Internal; namespace NATS.Client.Core; @@ -297,11 +299,15 @@ public void CopyTo(KeyValuePair[] array, int arrayIndex) /// /// Returns the bytes length of the header /// - /// an instance of headerWriter - /// bytes length of th header - public long GetBytesLength(HeaderWriter headerWriter) + /// Encoding used. Default to utf8 if not provided + /// Bytes length of the header + public long GetBytesLength(Encoding? encoding = null) { - return headerWriter.GetBytesLength(this); + // if null set to utf-8 + encoding = encoding ?? Encoding.UTF8; + + var len = HeaderWriter.GetBytesLength(this, encoding); + return len; } /// diff --git a/tests/NATS.Client.CoreUnit.Tests/NatsHeaderTest.cs b/tests/NATS.Client.CoreUnit.Tests/NatsHeaderTest.cs index a918a79de..f938451c5 100644 --- a/tests/NATS.Client.CoreUnit.Tests/NatsHeaderTest.cs +++ b/tests/NATS.Client.CoreUnit.Tests/NatsHeaderTest.cs @@ -206,8 +206,8 @@ public void GetBytesLengthTest() ["a-long-header-key"] = "value", ["key"] = "a-long-header-value", }; - var writer = new HeaderWriter(Encoding.UTF8); - var bytesLength = headers.GetBytesLength(writer); + var encoding = Encoding.UTF8; + var bytesLength = headers.GetBytesLength(encoding); var text = "NATS/1.0\r\nk1: v1\r\nk2: v2-0\r\nk2: v2-1\r\na-long-header-key: value\r\nkey: a-long-header-value\r\n\r\n"; var expected = new ReadOnlySequence(Encoding.UTF8.GetBytes(text)); From edb5cde15dadfc01b51233886fa026c0bb57e7f9 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Wed, 13 Nov 2024 00:15:42 +0000 Subject: [PATCH 4/4] Suppress MsQuicApi warnings in build output --- .github/workflows/test.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b10419d1e..f748435e5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -115,7 +115,9 @@ jobs: cd tests/NATS.Client.CheckNativeAot rm -rf bin obj - dotnet publish -r linux-x64 -c Release -o dist | tee output.txt + + # temporarily ignore MsQuicApi warnings + dotnet publish -r linux-x64 -c Release -o dist | grep -v MsQuicApi | tee output.txt # check for warnings grep -i warning output.txt && exit 1