Skip to content

Commit

Permalink
open Header.Writer class and create GetBytesLength method (#674)
Browse files Browse the repository at this point in the history
* open Header.Writer class and create GetBytesLength method

* create a convenience method on the header class

* making HeaderWriter internal again

* Suppress MsQuicApi warnings in build output

---------

Co-authored-by: Ziya Suzen <[email protected]>
  • Loading branch information
SiuTung08 and mtmk authored Nov 13, 2024
1 parent 5665476 commit 26ffb37
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 14 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
45 changes: 32 additions & 13 deletions src/NATS.Client.Core/Internal/HeaderWriter.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Buffers;
using System.IO.Pipelines;
using System.Text;
using NATS.Client.Core.Commands;

Expand All @@ -21,6 +20,37 @@ internal class HeaderWriter

private static ReadOnlySpan<byte> ColonSpace => new[] { ByteColon, ByteSpace };

internal static long GetBytesLength(NatsHeaders headers, Encoding encoding)
{
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<byte> bufferWriter, NatsHeaders headers)
{
bufferWriter.WriteSpan(CommandConstants.NatsHeaders10NewLine);
Expand All @@ -37,10 +67,7 @@ internal long Write(IBufferWriter<byte> 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;
Expand All @@ -53,9 +80,7 @@ internal long Write(IBufferWriter<byte> 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;
Expand All @@ -81,9 +106,7 @@ private static bool ValidateKey(ReadOnlySpan<byte> span)
foreach (var b in span)
{
if (b <= ByteSpace || b == ByteColon || b >= ByteDel)
{
return false;
}
}

return true;
Expand All @@ -96,15 +119,11 @@ private static bool ValidateValue(ReadOnlySpan<byte> 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..];
}
Expand Down
16 changes: 16 additions & 0 deletions src/NATS.Client.Core/NatsHeaders.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -294,6 +296,20 @@ public void CopyTo(KeyValuePair<string, StringValues>[] array, int arrayIndex)
}
}

/// <summary>
/// Returns the bytes length of the header
/// </summary>
/// <param name="encoding">Encoding used. Default to utf8 if not provided</param>
/// <returns>Bytes length of the header</returns>
public long GetBytesLength(Encoding? encoding = null)
{
// if null set to utf-8
encoding = encoding ?? Encoding.UTF8;

var len = HeaderWriter.GetBytesLength(this, encoding);
return len;
}

/// <summary>
/// Removes the given item from the the collection.
/// </summary>
Expand Down
19 changes: 19 additions & 0 deletions tests/NATS.Client.CoreUnit.Tests/NatsHeaderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 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<byte>(Encoding.UTF8.GetBytes(text));

Assert.Equal(expected.Length, bytesLength);
}
}

0 comments on commit 26ffb37

Please sign in to comment.