102 lines
3.1 KiB
C#
102 lines
3.1 KiB
C#
using System.Text;
|
|
|
|
namespace NATS.Server.Protocol;
|
|
|
|
public readonly struct NatsHeaders
|
|
{
|
|
public int Status { get; init; }
|
|
public string Description { get; init; }
|
|
public Dictionary<string, string[]> Headers { get; init; }
|
|
|
|
public static readonly NatsHeaders Invalid = new() { Status = -1, Description = string.Empty, Headers = new() };
|
|
}
|
|
|
|
public static class NatsHeaderParser
|
|
{
|
|
private static readonly byte[] CrLf = "\r\n"u8.ToArray();
|
|
private static readonly byte[] Prefix = "NATS/1.0"u8.ToArray();
|
|
|
|
public static NatsHeaders Parse(ReadOnlySpan<byte> data)
|
|
{
|
|
if (data.Length < Prefix.Length)
|
|
return NatsHeaders.Invalid;
|
|
|
|
if (!data[..Prefix.Length].SequenceEqual(Prefix))
|
|
return NatsHeaders.Invalid;
|
|
|
|
int pos = Prefix.Length;
|
|
int status = 0;
|
|
string description = string.Empty;
|
|
|
|
// Parse status line: NATS/1.0[ status[ description]]\r\n
|
|
int lineEnd = data[pos..].IndexOf(CrLf);
|
|
if (lineEnd < 0)
|
|
return NatsHeaders.Invalid;
|
|
|
|
var statusLine = data[pos..(pos + lineEnd)];
|
|
pos += lineEnd + 2; // skip \r\n
|
|
|
|
if (statusLine.Length > 0)
|
|
{
|
|
int si = 0;
|
|
while (si < statusLine.Length && statusLine[si] == (byte)' ')
|
|
si++;
|
|
|
|
int numStart = si;
|
|
while (si < statusLine.Length && statusLine[si] >= (byte)'0' && statusLine[si] <= (byte)'9')
|
|
si++;
|
|
|
|
if (si > numStart)
|
|
{
|
|
status = int.Parse(Encoding.ASCII.GetString(statusLine[numStart..si]));
|
|
|
|
while (si < statusLine.Length && statusLine[si] == (byte)' ')
|
|
si++;
|
|
if (si < statusLine.Length)
|
|
description = Encoding.ASCII.GetString(statusLine[si..]);
|
|
}
|
|
}
|
|
|
|
// Parse key-value headers until empty line
|
|
var headers = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
|
|
while (pos < data.Length)
|
|
{
|
|
var remaining = data[pos..];
|
|
if (remaining.Length >= 2 && remaining[0] == (byte)'\r' && remaining[1] == (byte)'\n')
|
|
break;
|
|
|
|
lineEnd = remaining.IndexOf(CrLf);
|
|
if (lineEnd < 0)
|
|
break;
|
|
|
|
var headerLine = remaining[..lineEnd];
|
|
pos += lineEnd + 2;
|
|
|
|
int colon = headerLine.IndexOf((byte)':');
|
|
if (colon < 0)
|
|
continue;
|
|
|
|
var key = Encoding.ASCII.GetString(headerLine[..colon]).Trim();
|
|
var value = Encoding.ASCII.GetString(headerLine[(colon + 1)..]).Trim();
|
|
|
|
if (!headers.TryGetValue(key, out var values))
|
|
{
|
|
values = [];
|
|
headers[key] = values;
|
|
}
|
|
values.Add(value);
|
|
}
|
|
|
|
var result = new Dictionary<string, string[]>(headers.Count, StringComparer.OrdinalIgnoreCase);
|
|
foreach (var (k, v) in headers)
|
|
result[k] = v.ToArray();
|
|
|
|
return new NatsHeaders
|
|
{
|
|
Status = status,
|
|
Description = description,
|
|
Headers = result,
|
|
};
|
|
}
|
|
}
|